From d5b67947f920296342e4a24bf6a95398d4ca3624 Mon Sep 17 00:00:00 2001 From: alexion Date: Sat, 18 Jul 2026 00:54:25 -0400 Subject: [PATCH 01/32] docs: establish domain model, MVI spec, and environment reference Capture the design work for the NixOS migration before any implementation: - .claude/CONTEXT.md: domain glossary (Host, Module, Skeleton, Auto-loader, Enable convention, unstable/stable overlay) - .claude/adr/0001-sops-nix-for-secrets.md: secrets tooling decision - .claude/spec/laptop-mvi.md: frozen minimum-viable-install spec for neogaia - reference/: read-only snapshot of the current CachyOS configs (secrets and state excluded), plus ENVIRONMENT.md profiling the live environment to guide replication --- .claude/CONTEXT.md | 33 +++ .claude/adr/0001-sops-nix-for-secrets.md | 10 + .claude/spec/laptop-mvi.md | 92 ++++++ reference/ENVIRONMENT.md | 89 ++++++ reference/README.md | 28 ++ reference/home/.bash_logout | 3 + reference/home/.bash_profile | 6 + reference/home/.bashrc | 11 + reference/home/.config/QtProject.conf | 5 + reference/home/.config/Trolltech.conf | 17 ++ .../home/.config/alacritty/alacritty.toml | 88 ++++++ reference/home/.config/dolphinrc | 10 + .../home/.config/fish/completions/bun.fish | 196 +++++++++++++ .../home/.config/fish/completions/dot.fish | 34 +++ .../.config/fish/completions/fishtape.fish | 2 + .../home/.config/fish/conf.d/aliases.fish | 4 + reference/home/.config/fish/conf.d/env.fish | 4 + .../home/.config/fish/conf.d/rustup.fish | 1 + reference/home/.config/fish/config.fish | 17 ++ reference/home/.config/fish/fish_plugins | 1 + .../home/.config/fish/functions/dot.fish | 130 ++++++++ .../home/.config/fish/functions/fishtape.fish | 116 ++++++++ reference/home/.config/fontconfig/fonts.conf | 79 +++++ .../.config/gtk-3.0/assets/close-active.svg | 33 +++ .../gtk-3.0/assets/close-backdrop-active.svg | 33 +++ .../gtk-3.0/assets/close-backdrop-hover.svg | 33 +++ .../gtk-3.0/assets/close-backdrop-normal.svg | 27 ++ .../.config/gtk-3.0/assets/close-hover.svg | 33 +++ .../.config/gtk-3.0/assets/close-normal.svg | 27 ++ .../gtk-3.0/assets/maximize-active.svg | 32 ++ .../assets/maximize-backdrop-active.svg | 32 ++ .../assets/maximize-backdrop-hover.svg | 32 ++ .../assets/maximize-backdrop-normal.svg | 26 ++ .../.config/gtk-3.0/assets/maximize-hover.svg | 32 ++ .../gtk-3.0/assets/maximize-normal.svg | 26 ++ .../gtk-3.0/assets/maximized-active.svg | 32 ++ .../assets/maximized-backdrop-active.svg | 32 ++ .../assets/maximized-backdrop-hover.svg | 32 ++ .../assets/maximized-backdrop-normal.svg | 26 ++ .../gtk-3.0/assets/maximized-hover.svg | 32 ++ .../gtk-3.0/assets/maximized-normal.svg | 26 ++ .../gtk-3.0/assets/minimize-active.svg | 32 ++ .../assets/minimize-backdrop-active.svg | 32 ++ .../assets/minimize-backdrop-hover.svg | 32 ++ .../assets/minimize-backdrop-normal.svg | 26 ++ .../.config/gtk-3.0/assets/minimize-hover.svg | 32 ++ .../gtk-3.0/assets/minimize-normal.svg | 26 ++ reference/home/.config/gtk-3.0/colors.css | 84 ++++++ reference/home/.config/gtk-3.0/gtk.css | 1 + reference/home/.config/gtk-3.0/settings.ini | 18 ++ .../.config/gtk-3.0/window_decorations.css | 71 +++++ reference/home/.config/gtk-4.0/colors.css | 84 ++++++ reference/home/.config/gtk-4.0/gtk.css | 1 + reference/home/.config/gtk-4.0/settings.ini | 13 + .../.config/gtk-4.0/window_decorations.css | 71 +++++ reference/home/.config/gtkrc-2.0 | 5 + reference/home/.config/kcminputrc | 2 + reference/home/.config/kdeglobals | 177 +++++++++++ reference/home/.config/kglobalshortcutsrc | 277 ++++++++++++++++++ reference/home/.config/konsolerc | 5 + reference/home/.config/kwinoutputconfig.json | 93 ++++++ reference/home/.config/kwinrc | 60 ++++ reference/home/.config/kxkbrc | 3 + .../colorschemes/catppuccin-frappe.micro | 42 +++ .../micro/colorschemes/catppuccin-latte.micro | 42 +++ .../colorschemes/catppuccin-macchiato.micro | 42 +++ .../micro/colorschemes/catppuccin-mocha.micro | 42 +++ reference/home/.config/micro/settings.json | 3 + reference/home/.config/mimeapps.list | 4 + .../.config/nvim/after/ftplugin/markdown.lua | 1 + reference/home/.config/nvim/init.lua | 3 + reference/home/.config/nvim/lazy-lock.json | 13 + reference/home/.config/nvim/lua/keys.lua | 8 + reference/home/.config/nvim/lua/plugin.lua | 23 ++ .../home/.config/nvim/lua/plugins/git.lua | 36 +++ .../.config/nvim/lua/plugins/navigation.lua | 27 ++ .../home/.config/nvim/lua/plugins/ui.lua | 55 ++++ .../home/.config/nvim/lua/vim_options.lua | 29 ++ .../plasma-org.kde.plasma.desktop-appletsrc | 173 +++++++++++ .../home/.config/powermanagementprofilesrc | 2 + reference/home/.config/shelly/config.json | 56 ++++ reference/home/.config/spectaclerc | 6 + reference/home/.config/tmux/tmux.conf | 74 +++++ reference/home/.config/user-dirs.dirs | 16 + .../home/.config/xsettingsd/xsettingsd.conf | 17 ++ reference/home/.gitconfig | 3 + reference/home/.gitignore | 8 + reference/home/.gtkrc-2.0 | 14 + reference/home/.profile | 1 + reference/home/.zshenv | 1 + reference/home/.zshrc | 1 + 91 files changed, 3409 insertions(+) create mode 100644 .claude/CONTEXT.md create mode 100644 .claude/adr/0001-sops-nix-for-secrets.md create mode 100644 .claude/spec/laptop-mvi.md create mode 100644 reference/ENVIRONMENT.md create mode 100644 reference/README.md create mode 100644 reference/home/.bash_logout create mode 100644 reference/home/.bash_profile create mode 100644 reference/home/.bashrc create mode 100644 reference/home/.config/QtProject.conf create mode 100644 reference/home/.config/Trolltech.conf create mode 100644 reference/home/.config/alacritty/alacritty.toml create mode 100644 reference/home/.config/dolphinrc create mode 100644 reference/home/.config/fish/completions/bun.fish create mode 100644 reference/home/.config/fish/completions/dot.fish create mode 100644 reference/home/.config/fish/completions/fishtape.fish create mode 100644 reference/home/.config/fish/conf.d/aliases.fish create mode 100644 reference/home/.config/fish/conf.d/env.fish create mode 100644 reference/home/.config/fish/conf.d/rustup.fish create mode 100644 reference/home/.config/fish/config.fish create mode 100644 reference/home/.config/fish/fish_plugins create mode 100644 reference/home/.config/fish/functions/dot.fish create mode 100644 reference/home/.config/fish/functions/fishtape.fish create mode 100644 reference/home/.config/fontconfig/fonts.conf create mode 100644 reference/home/.config/gtk-3.0/assets/close-active.svg create mode 100644 reference/home/.config/gtk-3.0/assets/close-backdrop-active.svg create mode 100644 reference/home/.config/gtk-3.0/assets/close-backdrop-hover.svg create mode 100644 reference/home/.config/gtk-3.0/assets/close-backdrop-normal.svg create mode 100644 reference/home/.config/gtk-3.0/assets/close-hover.svg create mode 100644 reference/home/.config/gtk-3.0/assets/close-normal.svg create mode 100644 reference/home/.config/gtk-3.0/assets/maximize-active.svg create mode 100644 reference/home/.config/gtk-3.0/assets/maximize-backdrop-active.svg create mode 100644 reference/home/.config/gtk-3.0/assets/maximize-backdrop-hover.svg create mode 100644 reference/home/.config/gtk-3.0/assets/maximize-backdrop-normal.svg create mode 100644 reference/home/.config/gtk-3.0/assets/maximize-hover.svg create mode 100644 reference/home/.config/gtk-3.0/assets/maximize-normal.svg create mode 100644 reference/home/.config/gtk-3.0/assets/maximized-active.svg create mode 100644 reference/home/.config/gtk-3.0/assets/maximized-backdrop-active.svg create mode 100644 reference/home/.config/gtk-3.0/assets/maximized-backdrop-hover.svg create mode 100644 reference/home/.config/gtk-3.0/assets/maximized-backdrop-normal.svg create mode 100644 reference/home/.config/gtk-3.0/assets/maximized-hover.svg create mode 100644 reference/home/.config/gtk-3.0/assets/maximized-normal.svg create mode 100644 reference/home/.config/gtk-3.0/assets/minimize-active.svg create mode 100644 reference/home/.config/gtk-3.0/assets/minimize-backdrop-active.svg create mode 100644 reference/home/.config/gtk-3.0/assets/minimize-backdrop-hover.svg create mode 100644 reference/home/.config/gtk-3.0/assets/minimize-backdrop-normal.svg create mode 100644 reference/home/.config/gtk-3.0/assets/minimize-hover.svg create mode 100644 reference/home/.config/gtk-3.0/assets/minimize-normal.svg create mode 100644 reference/home/.config/gtk-3.0/colors.css create mode 100644 reference/home/.config/gtk-3.0/gtk.css create mode 100644 reference/home/.config/gtk-3.0/settings.ini create mode 100644 reference/home/.config/gtk-3.0/window_decorations.css create mode 100644 reference/home/.config/gtk-4.0/colors.css create mode 100644 reference/home/.config/gtk-4.0/gtk.css create mode 100644 reference/home/.config/gtk-4.0/settings.ini create mode 100644 reference/home/.config/gtk-4.0/window_decorations.css create mode 100644 reference/home/.config/gtkrc-2.0 create mode 100644 reference/home/.config/kcminputrc create mode 100644 reference/home/.config/kdeglobals create mode 100644 reference/home/.config/kglobalshortcutsrc create mode 100644 reference/home/.config/konsolerc create mode 100644 reference/home/.config/kwinoutputconfig.json create mode 100644 reference/home/.config/kwinrc create mode 100644 reference/home/.config/kxkbrc create mode 100644 reference/home/.config/micro/colorschemes/catppuccin-frappe.micro create mode 100644 reference/home/.config/micro/colorschemes/catppuccin-latte.micro create mode 100644 reference/home/.config/micro/colorschemes/catppuccin-macchiato.micro create mode 100644 reference/home/.config/micro/colorschemes/catppuccin-mocha.micro create mode 100644 reference/home/.config/micro/settings.json create mode 100644 reference/home/.config/mimeapps.list create mode 100644 reference/home/.config/nvim/after/ftplugin/markdown.lua create mode 100644 reference/home/.config/nvim/init.lua create mode 100644 reference/home/.config/nvim/lazy-lock.json create mode 100644 reference/home/.config/nvim/lua/keys.lua create mode 100644 reference/home/.config/nvim/lua/plugin.lua create mode 100644 reference/home/.config/nvim/lua/plugins/git.lua create mode 100644 reference/home/.config/nvim/lua/plugins/navigation.lua create mode 100644 reference/home/.config/nvim/lua/plugins/ui.lua create mode 100644 reference/home/.config/nvim/lua/vim_options.lua create mode 100644 reference/home/.config/plasma-org.kde.plasma.desktop-appletsrc create mode 100644 reference/home/.config/powermanagementprofilesrc create mode 100644 reference/home/.config/shelly/config.json create mode 100644 reference/home/.config/spectaclerc create mode 100644 reference/home/.config/tmux/tmux.conf create mode 100644 reference/home/.config/user-dirs.dirs create mode 100644 reference/home/.config/xsettingsd/xsettingsd.conf create mode 100644 reference/home/.gitconfig create mode 100644 reference/home/.gitignore create mode 100644 reference/home/.gtkrc-2.0 create mode 100644 reference/home/.profile create mode 100644 reference/home/.zshenv create mode 100644 reference/home/.zshrc diff --git a/.claude/CONTEXT.md b/.claude/CONTEXT.md new file mode 100644 index 0000000..edd69b9 --- /dev/null +++ b/.claude/CONTEXT.md @@ -0,0 +1,33 @@ +# NixOS Dotfiles + +A single flake that builds every machine the user owns — laptop, desktop, and three servers — from one shared, modular configuration. + +## Language + +**Host**: +One physical machine the flake builds a NixOS configuration for. Each Host has a directory under `hosts/` holding its machine-specific `hardware-configuration.nix` and its choice of enabled Modules. +_Avoid_: machine, node, system, box + +**Module**: +A single `.nix` feature file under `modules/` that declares an `enable` option and the configuration it turns on. Every Module is always imported but stays inert until a Host enables it. +_Avoid_: component, package, plugin + +**Skeleton**: +The flake's plumbing — the Auto-loader, the helper lib, the flake inputs/overlays, and the shared base config — as distinct from the Modules that sit on top of it. +_Avoid_: framework, core, base, scaffolding + +**Auto-loader**: +The lib code that recursively discovers and imports every Module under `modules/` (and every Host under `hosts/`) so new files wire themselves in without manual `imports` edits. +_Avoid_: loader, importer, scanner + +**Enable convention**: +The rule that every Module is imported unconditionally and guards its own body with `mkIf config.modules..enable`, so a Host reads as a checklist of `enable = true` flags. +_Avoid_: feature flag, toggle, opt-in + +**unstable overlay**: +The overlay exposing `nixpkgs-unstable` packages as `unstable.`, used to pull an individual package fresher than the `nixos-unstable` base. +_Avoid_: bleeding-edge, latest + +**stable overlay**: +The overlay exposing the latest stable release (`nixos-25.05`) as `stable.`, used to pin an individual package to the rock-solid release from the `nixos-unstable` base. +_Avoid_: LTS, release channel diff --git a/.claude/adr/0001-sops-nix-for-secrets.md b/.claude/adr/0001-sops-nix-for-secrets.md new file mode 100644 index 0000000..3b53ec4 --- /dev/null +++ b/.claude/adr/0001-sops-nix-for-secrets.md @@ -0,0 +1,10 @@ +# Use sops-nix for secrets + +The repo is public, so no secret — including password hashes and the WireGuard/ProtonVPN key — may be committed in plaintext. We manage all secrets with **sops-nix**: encrypted into the repo and decrypted per-host at activation via an age key derived from each machine's SSH host key. + +We chose sops-nix over agenix for its multi-recipient encryption (one secret readable by both a host and the admin laptop) and its grouped-file editing workflow, which scale better across the planned five hosts with a mix of shared and per-host secrets. The cost is slightly more upfront machinery than agenix's one-file-per-secret model. + +## Consequences + +- User/root passwords use `hashedPasswordFile` backed by a sops secret, never a committed hash. +- Each new host must have its SSH host public key registered as a recipient before it can decrypt its secrets. diff --git a/.claude/spec/laptop-mvi.md b/.claude/spec/laptop-mvi.md new file mode 100644 index 0000000..f97bf43 --- /dev/null +++ b/.claude/spec/laptop-mvi.md @@ -0,0 +1,92 @@ +## Problem Statement + +I'm returning to NixOS after ~2 years away, and I want to start by moving my laptop (`neogaia`, a Dell XPS 13 9380 currently running CachyOS) onto it. My old config still exists but is stale and written in a style I no longer want to copy verbatim. Eventually this same config has to grow to cover my desktop and three servers, so whatever I build for the laptop has to be a clean, scalable foundation — not a throwaway. + +Reimaging the laptop is destructive and I only get one machine, so I need a tightly-scoped, well-understood **minimum viable install (MVI)**: the smallest config that boots the laptop into a usable state I can then iterate on live, without risking a half-defined system that strands me at a dead console. + +## Solution + +Rebuild the `Skeleton` and a single `neogaia` `Host` to the point where the laptop: + +- boots from an encrypted disk (LUKS + btrfs + zram), +- comes up on wifi, +- lets me log into a console as my user and run `nixos-rebuild switch`, +- and already carries my core terminal tooling (fish, tmux, nvim, Claude Code). + +Everything graphical and everything multi-host is deliberately left for later iterative passes, which are safe because a mistake then is "edit and rebuild," not "reimage." The MVI is the one step that must be right *before* reimaging; the rest is reversible. + +The install itself is done from the NixOS live ISO by cloning the repo from my Gitea and running a single `disko-install` against the `neogaia` `Host`, then setting a bootstrap password by hand. + +## User Stories + +1. As the operator, I want the `Skeleton` rewritten around my old scalable ideas (the `Auto-loader`, the `Enable convention`, per-`Host` layout), so that the config stays legible and shareable across all five future machines without me copying stale code. +2. As the operator, I want the flake hand-rolled and cleaned up (no framework layer), so that the whole plumbing stays readable in one place for a config that only targets a handful of `x86_64-linux` machines. +3. As the operator, I want every `Module` auto-discovered and imported but inert until a `Host` sets its `enable` flag, so that each `Host` reads as a checklist of features. +4. As the operator, I want a `nixos-unstable` base with an `unstable overlay` and a `stable overlay`, so that I can run rolling by default but reach up to bleeding-edge or down to rock-solid on a per-package basis. +5. As the operator, I want home-manager integrated as a NixOS module with global packages, so that one `nixos-rebuild switch` builds both the system and my user environment atomically. +6. As the operator, I want my user modelled as an explicit option defaulting to `alexion` (no impure environment lookup), so that the config is reproducible and honest about who the user is. +7. As the operator, I want the laptop's disk declared with `disko` as encrypted btrfs plus zram swap, so that the install is reproducible and the laptop is encrypted at rest. +8. As the operator, I want the system to prompt for the LUKS passphrase at boot via systemd-boot and the initrd, so that the encrypted disk unlocks on a normal boot. +9. As the operator, I want the CachyOS kernel from chaotic-nyx with the chaotic binary cache wired in from the first build, so that I get the performance/feel I'm used to without compiling the kernel from source. +10. As the operator, I want Intel microcode and the redistributable firmware for the QCA6174 wifi included, so that the laptop's hardware works out of the box. +11. As the operator, I want NetworkManager enabled, so that I can join wifi easily from the console. +12. As the operator, I want an SSH daemon running, so that I can drive the rest of the setup remotely if the console is inconvenient. +13. As the operator, I want my user in `wheel` with a manually-set bootstrap password, so that I can log in and use sudo on first boot without committing any secret to a public repo. +14. As the operator, I want fish as my default login shell, configured natively via home-manager with my `cachyos-config.fish` translated (greeting, bat-manpager, `done` and bang-bang plugins, helper functions, eza/nav aliases) and all Arch/pacman-specific parts dropped or replaced with NixOS equivalents, so that my shell feels like home but is correct for NixOS. +15. As the operator, I want tmux configured natively via home-manager using my exact existing `tmux.conf` text, so that my terminal multiplexer is identical to today with no plugin manager needed. +16. As the operator, I want my nvim config brought in verbatim (lazy.nvim managing its own plugins) via a writable out-of-store symlink, with `git`/`gcc`/`ripgrep`/`fd` provided by Nix, so that my editor is identical to today and lazy.nvim can still update and write its lockfile. +17. As the operator, I want Claude Code installed declaratively and authenticatable without a browser on the laptop, so that I can use it over the console/SSH via the paste-code flow or an API key. +18. As the operator, I want timezone `America/New_York`, locale `en_GB.UTF-8`, and console keymap `us` set, so that the base system matches my locale preferences. +19. As the operator, I want to install by cloning the repo from my Gitea onto the live ISO and running `disko-install` against `neogaia`, so that I avoid self-signed-TLS/auth problems with flake fetching during install. +20. As the operator, I want the `Skeleton` designed so that per-`Host` disk layouts, per-`Host` kernels, and preserved ZFS pools are all expressible, so that the same foundation extends to the desktop and the three servers later without restructuring. + +## Implementation Decisions + +**Skeleton** +- Hand-rolled flake, rewritten and trimmed; no flake-parts. +- `Auto-loader` rewritten: recursively discovers and imports every `Module` under the modules tree without the old null-placeholder traversal hack; a single discovery helper feeds the `Host` imports. The old `nixosModules` flake output is dropped. +- Helper lib trimmed to the `Auto-loader`, the host-builder, and the script-from-file helper. `with lib.my` replaced by explicit `inherit`s throughout. `enable` flags use the stdlib enable-option helper rather than bespoke sugar. +- `nixos-unstable` as the base channel. An `unstable overlay` exposes `nixpkgs-unstable` packages; a `stable overlay` exposes the latest stable release (`nixos-25.05`). chaotic-nyx added as an input with its overlay and binary cache from the start. +- home-manager sourced from `nix-community`, tracking master with nixpkgs followed, integrated as a NixOS module with global packages and user packages. +- User modelled as an explicit option defaulting to `alexion`, in `wheel`, driving the system user and the home-manager user in lockstep. + +**neogaia Host** +- Disk declared via `disko`: LUKS-encrypted btrfs with subvolumes plus zram swap. systemd-boot on an EFI system partition; initrd LUKS unlock. +- CachyOS kernel selected via a small per-`Host` kernel mechanism; chaotic substituter and trusted key in the Nix settings. +- Intel microcode; redistributable firmware enabled for the QCA6174 wifi. NetworkManager for networking. A zram toggle `Module` enabled here. +- SSH daemon enabled. Baseline CLI (git, editor, flakes) present. Claude Code installed declaratively. +- fish `Module`: native home-manager configuration; translated aliases/functions/plugins/init; set as the default login shell. tmux `Module`: native home-manager, exact existing config text inlined. nvim `Module`: verbatim config placed as a writable out-of-store symlink with runtime dependencies provided by Nix. +- Locale, timezone, and keymap set to the detected values. + +**Install flow** +- Repo pushed to Gitea first. From the NixOS live ISO: join wifi, clone the repo locally, run `disko-install` against the `neogaia` `Host` with the chaotic substituter passed to the install-time daemon, set bootstrap passwords via `nixos-enter`, reboot. + +**Secrets (design only in MVI)** +- Per ADR 0001, secrets use `sops-nix` with age keys derived from each `Host`'s SSH host key. The MVI does not wire any secret, because a `Host`'s age key does not exist until its first install generates the SSH host key. The bootstrap password is set by hand and never committed; moving passwords to a `hashedPasswordFile` backed by a sops secret is the first post-boot task, out of scope here. + +## Testing Decisions + +- A good test here asserts externally-observable evaluation/build success of the whole `Host`, not the internals of any individual `Module`. +- **Primary seam (required):** the `neogaia` `Host` evaluates and its system toplevel builds. Building the toplevel drives the entire `Skeleton` — the `Auto-loader` discovering every `Module`, all three overlays resolving, home-manager integration, and every enabled module's config merging without conflict — plus the `disko` layout, which builds from the same tree. Nearly all config-authoring errors surface at this seam short of booting real hardware. +- No unit-level tests of individual modules; the config-merge model makes the whole-`Host` build the meaningful unit, and it is the highest available seam. +- Prior art: none in this repo yet (it starts empty); this build-the-toplevel check is the pattern to reuse for every future `Host`. +- The genuine end-to-end confirmation is the real reimage, which is manual and irreversible by nature and is not automated. + +## Out of Scope + +- Any graphical environment: Wayland-vs-i3 choice, greeter/display manager, theming (Nord via Stylix or otherwise), fonts, terminal emulator, browser, general desktop apps, gaming (Steam/Lutris/proton-cachyos), emulation. +- Full `sops-nix` wiring and moving passwords off the bootstrap value (immediate post-boot follow-up, but not MVI). +- Migrating nvim to a native home-manager configuration with Nix-managed plugins. +- chaotic-nyx packages beyond the kernel (`mesa-git`, `proton-cachyos`, `scx` schedulers). +- Flatpak strategy (`nix-flatpak` vs dropping the old imperative helper). +- The desktop `Host` (`zeus`), including Nvidia. +- The three servers: deployment model, service migration (plex/arr/kavita/nfs/torrent-through-protonvpn), ZFS wiring and pool import, backups/monitoring, and per-server kernel/channel pinning. +- VM-based CI (`nixosTest` boot assertions) — explicitly a future addition, not part of this deliverable. + +## Further Notes + +- **Bootstrap ordering:** the flake must exist on Gitea before the install can consume it, and the manual password step keeps the public repo free of any secret while still yielding a login on first boot. +- **Gitea is a bootstrap dependency:** every NixOS install pulls the config from self-hosted Gitea, so the Gitea host must stay reachable during any install — relevant when sequencing the servers so the migration never locks the operator out of their own configs. +- **chaotic cache at install time:** the install-time Nix daemon on the live ISO must have the chaotic substituter configured, or it compiles the CachyOS kernel from source on the USB stick. +- **Extends to future Hosts by construction:** disk layout, kernel, and channel are all per-`Host` concerns in the `Skeleton`, and existing ZFS pools are preserved by import rather than declared through `disko`. This is what lets the desktop and the three servers join later without reworking the foundation. +- **Theme target is Nord** (the current CachyOS setup is Nord across terminal, tmux, and nvim), superseding the old repo's Dracula — relevant when the theming branch is grilled. diff --git a/reference/ENVIRONMENT.md b/reference/ENVIRONMENT.md new file mode 100644 index 0000000..718e16c --- /dev/null +++ b/reference/ENVIRONMENT.md @@ -0,0 +1,89 @@ +# Current environment profile — `neogaia` (CachyOS) + +A reconstruction of the live CachyOS environment on the laptop, captured before the NixOS migration. +This exists to define what "replicate as closely as possible" concretely means, and to feed the desktop-environment grill branch later. +It is descriptive, not a plan — nothing here is a decision. + +Source: the snapshot under `reference/home/`, plus `pacman -Qqett` (167 explicit top-level packages; no AUR, no Flatpaks). + +## In one line + +KDE Plasma 6 on Wayland, hand-configured to behave like an i3-style keyboard-driven tiling setup, wrapped around a tightly-integrated Alacritty + tmux + Neovim terminal workflow themed in Nord. + +## Desktop environment & workflow + +- **Plasma 6 on Wayland** (Plasma-6 migration markers, Xwayland scaling, powerdevil-6, SDDM via `plasma-login-manager`). +- Configured as a **tiling, keyboard-driven WM**: + - **9 virtual desktops**; **`Meta+1…9`** switch, **`Meta+Shift+1…9`** send-window (both custom — not Plasma defaults). + - **KWin built-in tiling** on every desktop, **0.25 / 0.5 / 0.25** columns; `Meta+T` tile editor. + - **`Meta+Shift+Q`** close window (i3 muscle memory). + - Quick-tile `Meta+arrows`; overview `Meta+W`; grid `Meta+G`; peek desktop `Meta+D`. + - **`Caps Lock → Escape`** (`caps:escape_shifted_capslock`). +- Fast animations (`AnimationDurationFactor=0.25`); touchpad clickfinger. + +This is effectively *Plasma-as-a-tiling-WM*. +The future desktop branch therefore forks between **Plasma 6 + this exact config** and a **dedicated Wayland tiler** (Hyprland / niri / sway) reproducing the same i3-like behaviour. + +## Terminal workflow (the core) + +One integrated, SSH-friendly, keyboard-only system: + +- **Alacritty** — MesloLGS Nerd Font Mono 12, opacity 0.8, Nord palette, save-to-clipboard, middle-click paste. +- **tmux** — `Ctrl-Space` prefix, vi mode, **OSC52** clipboard, Nord status bar, no plugins. +- **Neovim** — lazy.nvim; leader Space; **OSC52** clipboard; relativenumber; 2-space expandtab; undofile; smartcase; plugins: snacks picker, oil, neogit/gitsigns/diffview, which-key, treesitter, render-markdown, `nord.nvim`. +- Shared idioms: **`Ctrl+hjkl`** navigation across tmux panes and nvim splits; OSC52 everywhere (no `wl-copy`/`xclip`, survives SSH). + +## Theming & fonts (not unified — a replication decision point) + +- **DE chrome:** Breeze **Dark** — Qt + GTK (`Breeze`, prefer-dark) + `breeze-dark` icons + `breeze_cursors`. +- **Terminals/editor:** **Nord**. +- **micro:** catppuccin-macchiato (a third theme). +- **Installed but inactive:** `cachyos-nord-kde-theme-git` — a Nord Plasma theme is already available, so unifying the DE onto Nord is plausible. +- **Fonts:** UI Noto Sans 14; KDE mono Hack 14; terminal mono MesloLGS Nerd Font Mono. 96 DPI, 1× scale, slight hinting, antialias on, subpixel none. + +## Home layout & environment + +- Custom short XDG dirs: `~/dwn ~/doc ~/mus ~/pic ~/vid ~/wrk`; desktop hidden at `~/.desktop`; templates/public → `~/.ignoreme`. +- `EDITOR=nvim`; aliases `vi/vim→nvim`, `tmx=tmux new-session -A -s`, `cp -v`. +- git identity: **alexion / contact@alexion.dev**. + +## Toolchains (currently impure — likely "exceptions" on NixOS) + +- **Rust** via rustup (`~/.cargo`), **bun** (`~/.bun`), **Node** via a manual `~/.local/opt` unpack, **Android SDK** (`~/Android/Sdk`) on PATH. +- On NixOS these want a decision: Nix-native (nixpkgs / fenix / oxalica / etc.) vs. keeping the imperative installers. + +## Package inventory (categorised, meaningful subset) + +**Terminal / shell / editor:** alacritty, neovim, kate, micro, fish, zsh, claude-code, tea (Gitea CLI), meld. +**Browser:** firefox. +**Dev / infra:** docker, vscodium, base-devel, whisper-cpp-vulkan (backs the open-whispr dictation app). +**KDE apps:** dolphin, ark, kcalc, konsole, gwenview, haruna (mpv-based video), filelight, spectacle (screenshots), kdeconnect, kwalletmanager, partitionmanager, kinfocenter, plasma-systemmonitor, kscreen. +**Media codecs:** vlc-plugins-all, gst-plugins-{bad,ugly,va,pipewire}, gst-libav, libdvdcss, ffmpegthumbs. +**Fonts:** ttf-meslo-nerd, ttf-opensans, cantarell-fonts, noto-fonts-cjk, gsfonts, awesome-terminal-fonts. +**Networking:** networkmanager-openvpn, plasma-nm, wireguard-tools, nfs-utils, iwd, dnsmasq, bind. +**Hardware / firmware:** intel-ucode, intel-media-sdk, linux-firmware, sof-firmware, alsa-*, bluez-* + bluedevil, fwupd, cpupower, power-profiles-daemon, realtime-privileges. +**Filesystem / snapshots:** btrfs-assistant, snapper (`cachyos-snapper-support`), limine-snapper-sync, plus a broad set of fs tools (f2fs/xfs/jfs/nilfs/exfat/lvm2/dmraid) shipped by CachyOS. +**Printing:** cups-pdf, gutenprint, foomatic-db*, system-config-printer (full stack — verify it's actually used). +**CLI utils:** btop, glances, duf, tree, plocate, rsync, wget, unzip/unrar, pv, hwinfo. +**CachyOS-specific (won't port; NixOS equivalents or drop):** cachyos-* (settings, hooks, mirrorlists, kernel-manager, fish/zsh/micro configs, KDE themes, plymouth, wallpapers), cachy-update, shelly, reflector, rebuild-detector. + +## Notable current features to consider replicating + +- **btrfs + snapper snapshots** integrated into the boot menu (via Limine + `limine-snapper-sync`). On NixOS the analogue is generations (built-in) plus optionally snapper/btrbk for data snapshots — a future decision, not MVI. +- **KDE Connect** (phone integration), **Bluetooth** (bluedevil), **WireGuard** tooling, **Docker**, **KWallet** (PAM-unlocked). +- **Printing stack** fully installed. +- **fwupd** firmware updates. + +## Explicitly absent (don't assume from the old NixOS repo) + +- **No gaming** — no Steam / Lutris / Wine / Proton on this laptop (the old repo's `neogaia` had Steam; current reality does not). Gaming is a desktop (`zeus`) concern. +- **No Discord / Spotify / Slack** currently installed. +- **No emulation** stack (the old repo's retroarch/3ds/ps2 are not present here). + +## Open replication decisions this surfaces (for later grilling) + +1. **Plasma 6 Wayland (replicate config)** vs. **dedicated Wayland tiler** (Hyprland/niri/sway). +2. **Theme:** keep Breeze-Dark-DE + Nord-terminals as-is, or unify on Nord (Stylix)? +3. **Toolchains:** Nix-native Rust/Node/bun/Android vs. keep imperative. +4. **btrfs snapshots:** snapper/btrbk on NixOS, or rely on generations alone? +5. **Which apps are actually wanted** on the laptop vs. artefacts of the CachyOS default install (printing, the broad fs-tools set, etc.). diff --git a/reference/README.md b/reference/README.md new file mode 100644 index 0000000..2d8490a --- /dev/null +++ b/reference/README.md @@ -0,0 +1,28 @@ +# reference/ + +A frozen snapshot of the live CachyOS environment on `neogaia`, captured before the NixOS migration. + +This is a **read-only reference**, not part of the flake. +Nothing here is imported or built — it exists so the NixOS rebuild can be diffed against the environment it replaces. +Files mirror their real home paths under `home/` (e.g. `home/.config/nvim/` was `~/.config/nvim/`). + +## What's here + +- **Terminal / shell / editor:** alacritty, fish (minus `fish_variables`), tmux, nvim, micro (`settings.json` + colorschemes only). +- **Shell RCs:** bash and zsh rc/profile files, `.profile`. +- **KDE Plasma (curated):** the meaningful config (`kdeglobals`, `kwinrc`, `kglobalshortcutsrc`, `kxkbrc`, `kcminputrc`, panel applets, konsole, dolphin, spectacle, power management, monitor layout) — pure runtime state was skipped. +- **Theming / fonts:** gtk-3.0, gtk-4.0, gtkrc-2.0, fontconfig, xsettingsd, Qt config. +- **Misc:** `mimeapps.list`, `user-dirs.dirs`, `shelly/config.json`, `.gitconfig`, `.gitignore`. + +## What was deliberately excluded + +- **Secrets / keys:** `.ssh`, `.claude*`, `tea` (Gitea token), `kdeconnect` (device keys), `libaccounts-glib`, `kwalletrc`, `.pki`. +- **Browser data:** mozilla profiles. +- **Electron app state:** open-whispr (230M, contained a `.env` and encrypted keys), obsidian (per-vault config lives in each vault), VSCodium (no user `settings.json` existed — only default state). +- **Caches / runtime state:** `.cache`, `.npm`, `.cargo`, `.rustup`, `.local`, `.var`, dconf, pulse, micro's shipped `syntax/` defs, `*.bak`. + +## Known cleanups the rebuild must apply + +- fish `config.fish` sources CachyOS-only `cachyos-config.fish`, and hardcodes impure `~/.bun` / `~/.local/opt/node` PATHs. +- fish aliases include Arch/pacman-specific entries that don't apply on NixOS. +- The KDE and Arch package-manager (`shelly`) configs are environment-specific and only partially relevant. diff --git a/reference/home/.bash_logout b/reference/home/.bash_logout new file mode 100644 index 0000000..0e4e4f1 --- /dev/null +++ b/reference/home/.bash_logout @@ -0,0 +1,3 @@ +# +# ~/.bash_logout +# diff --git a/reference/home/.bash_profile b/reference/home/.bash_profile new file mode 100644 index 0000000..aeac56a --- /dev/null +++ b/reference/home/.bash_profile @@ -0,0 +1,6 @@ +# +# ~/.bash_profile +# + +[[ -f ~/.bashrc ]] && . ~/.bashrc +. "$HOME/.cargo/env" diff --git a/reference/home/.bashrc b/reference/home/.bashrc new file mode 100644 index 0000000..e96b5d6 --- /dev/null +++ b/reference/home/.bashrc @@ -0,0 +1,11 @@ +# +# ~/.bashrc +# + +# If not running interactively, don't do anything +[[ $- != *i* ]] && return + +alias ls='ls --color=auto' +alias grep='grep --color=auto' +PS1='[\u@\h \W]\$ ' +. "$HOME/.cargo/env" diff --git a/reference/home/.config/QtProject.conf b/reference/home/.config/QtProject.conf new file mode 100644 index 0000000..f61be5f --- /dev/null +++ b/reference/home/.config/QtProject.conf @@ -0,0 +1,5 @@ +[FileDialog] +history=@Invalid() +lastVisited=file:///home/alexion/doc/notes/ +qtVersion=6.11.1 +viewMode=Detail diff --git a/reference/home/.config/Trolltech.conf b/reference/home/.config/Trolltech.conf new file mode 100644 index 0000000..4a66f4a --- /dev/null +++ b/reference/home/.config/Trolltech.conf @@ -0,0 +1,17 @@ +[qt] +GUIEffects=none +KDE\contrast=4 +KWinPalette\activeBackground=#272c31 +KWinPalette\activeBlend=#fcfcfc +KWinPalette\activeForeground=#fcfcfc +KWinPalette\activeTitleBtnBg=#202326 +KWinPalette\frame=#202326 +KWinPalette\inactiveBackground=#202428 +KWinPalette\inactiveBlend=#a1a9b1 +KWinPalette\inactiveForeground=#a1a9b1 +KWinPalette\inactiveFrame=#202326 +KWinPalette\inactiveTitleBtnBg=#202326 +Palette\active=#fcfcfc, #292c30, #393e43, #2e3337, #131516, #1c1f21, #fcfcfc, #ffffff, #fcfcfc, #141618, #202326, #0e0f10, #3daee9, #fcfcfc, #1d99f3, #9b59b6, #1d1f22, #000000, #292c30, #fcfcfc, #a1a9b1, #3daee9 +Palette\disabled=#686a6c, #272a2e, #383d42, #2d3136, #121415, #1b1d20, #606263, #ffffff, #6d6f72, #131517, #1f2124, #0d0e0f, #1f2124, #686a6c, #164160, #402b4c, #1c1e20, #000000, #292c30, #fcfcfc, #42464a, #1f2124 +Palette\inactive=#fcfcfc, #292c30, #393e43, #2e3337, #131516, #1c1f21, #fcfcfc, #ffffff, #fcfcfc, #141618, #202326, #0e0f10, #1b4155, #fcfcfc, #1d99f3, #9b59b6, #1d1f22, #000000, #292c30, #fcfcfc, #a1a9b1, #1b4155 +font="Noto Sans,14,-1,5,400,0,0,0,0,0,0,0,0,0,0,1,,0,0" diff --git a/reference/home/.config/alacritty/alacritty.toml b/reference/home/.config/alacritty/alacritty.toml new file mode 100644 index 0000000..7411716 --- /dev/null +++ b/reference/home/.config/alacritty/alacritty.toml @@ -0,0 +1,88 @@ +[general] +working_directory = "None" +live_config_reload = true + +[env] +TERM = "xterm-256color" +WINIT_X11_SCALE_FACTOR = "1.0" + +[window] +dimensions = { columns = 100, lines = 30 } +dynamic_padding = true +decorations = "Full" +opacity = 0.8 +title = "Alacritty@CachyOS" +class = { instance = "Alacritty", general = "Alacritty" } +decorations_theme_variant = "Dark" + +[scrolling] +history = 10000 +multiplier = 3 + +[font] +normal = { family = "MesloLGS Nerd Font Mono", style = "Regular" } +bold = { family = "MesloLGS Nerd Font Mono", style = "Bold" } +italic = { family = "MesloLGS Nerd Font Mono", style = "Italic" } +bold_italic = { family = "MesloLGS Nerd Font Mono", style = "Bold Italic" } +size = 12.0 + +[colors] +draw_bold_text_with_bright_colors = true + +[colors.primary] +background = "0x2E3440" +foreground = "0xD8DEE9" + +[colors.normal] +black = "0x3B4252" +red = "0xBF616A" +green = "0xA3BE8C" +yellow = "0xEBCB8B" +blue = "0x81A1C1" +magenta = "0xB48EAD" +cyan = "0x88C0D0" +white = "0xE5E9F0" + +[colors.bright] +black = "0x4C566A" +red = "0xBF616A" +green = "0xA3BE8C" +yellow = "0xEBCB8B" +blue = "0x81A1C1" +magenta = "0xB48EAD" +cyan = "0x8FBCBB" +white = "0xECEFF4" + +[selection] +semantic_escape_chars = ",│`|:\"' ()[]{}<>\t" +save_to_clipboard = true + +[cursor] +style = { shape = "Underline", blinking = "Off" } +unfocused_hollow = true +thickness = 0.15 + +[mouse] +hide_when_typing = true +bindings = [ +{ mouse = "Middle", mods = "None", action = "PasteSelection" }, +] + +[keyboard] +bindings = [ +{ key = "Paste", mods = "None", action = "Paste" }, +{ key = "Copy", mods = "None", action = "Copy" }, +{ key = "L", mods = "Control", action = "ClearLogNotice" }, +{ key = "L", mods = "Control", mode = "~Vi", chars = "\f" }, +{ key = "PageUp", mods = "Shift", mode = "~Alt", action = "ScrollPageUp" }, +{ key = "PageDown", mods = "Shift", mode = "~Alt", action = "ScrollPageDown" }, +{ key = "Home", mods = "Shift", mode = "~Alt", action = "ScrollToTop" }, +{ key = "End", mods = "Shift", mode = "~Alt", action = "ScrollToBottom" }, +{ key = "V", mods = "Control|Shift", action = "Paste" }, +{ key = "C", mods = "Control|Shift", action = "Copy" }, +{ key = "F", mods = "Control|Shift", action = "SearchForward" }, +{ key = "B", mods = "Control|Shift", action = "SearchBackward" }, +{ key = "C", mods = "Control|Shift", mode = "Vi", action = "ClearSelection" }, +{ key = "Key0", mods = "Control", action = "ResetFontSize" }, +] + diff --git a/reference/home/.config/dolphinrc b/reference/home/.config/dolphinrc new file mode 100644 index 0000000..69c76b7 --- /dev/null +++ b/reference/home/.config/dolphinrc @@ -0,0 +1,10 @@ +[General] +Version=202 +ViewPropsTimestamp=2026,7,3,19,16,13.691 + +[KFileDialog Settings] +Places Icons Auto-resize=false +Places Icons Static Size=22 + +[MainWindow] +MenuBar=Disabled diff --git a/reference/home/.config/fish/completions/bun.fish b/reference/home/.config/fish/completions/bun.fish new file mode 100644 index 0000000..6b2dc5b --- /dev/null +++ b/reference/home/.config/fish/completions/bun.fish @@ -0,0 +1,196 @@ +# This is terribly complicated +# It's because: +# 1. bun run has to have dynamic completions +# 2. there are global options +# 3. bun {install add remove} gets special options +# 4. I don't know how to write fish completions well +# Contributions very welcome!! + +function __fish__get_bun_bins + string split ' ' (bun getcompletes b) +end + +function __fish__get_bun_scripts + set -lx SHELL bash + set -lx MAX_DESCRIPTION_LEN 40 + string trim (string split '\n' (string split '\t' (bun getcompletes z))) +end + +function __fish__get_bun_packages + if test (commandline -ct) != "" + set -lx SHELL fish + string split ' ' (bun getcompletes a (commandline -ct)) + end +end + +function __history_completions + set -l tokens (commandline --current-process --tokenize) + history --prefix (commandline) | string replace -r \^$tokens[1]\\s\* "" | string replace -r \^$tokens[2]\\s\* "" | string split ' ' +end + +function __fish__get_bun_bun_js_files + string split ' ' (bun getcompletes j) +end + +set -l bun_install_boolean_flags yarn production optional development no-save dry-run force no-cache silent verbose global +set -l bun_install_boolean_flags_descriptions "Write a yarn.lock file (yarn v1)" "Don't install devDependencies" "Add dependency to optionalDependencies" "Add dependency to devDependencies" "Don't update package.json or save a lockfile" "Don't install anything" "Always request the latest versions from the registry & reinstall all dependencies" "Ignore manifest cache entirely" "Don't output anything" "Excessively verbose logging" "Use global folder" + +set -l bun_builtin_cmds_without_run dev create help bun upgrade discord install remove add update init pm x repl +set -l bun_builtin_cmds_accepting_flags create help bun upgrade discord run init link unlink pm x update + +function __bun_complete_bins_scripts --inherit-variable bun_builtin_cmds_without_run -d "Emit bun completions for bins and scripts" + # Do nothing if we already have a builtin subcommand, + # or any subcommand other than "run". + if __fish_seen_subcommand_from $bun_builtin_cmds_without_run + or not __fish_use_subcommand && not __fish_seen_subcommand_from run + return + end + # Do we already have a bin or script subcommand? + set -l bins (__fish__get_bun_bins) + if __fish_seen_subcommand_from $bins + return + end + # Scripts have descriptions appended with a tab separator. + # Strip off descriptions for the purposes of subcommand testing. + set -l scripts (__fish__get_bun_scripts) + if __fish_seen_subcommand_from (string split \t -f 1 -- $scripts) + return + end + # Emit scripts. + for script in $scripts + echo $script + end + # Emit binaries and JS files (but only if we're doing `bun run`). + if __fish_seen_subcommand_from run + for bin in $bins + echo "$bin"\t"package bin" + end + for file in (__fish__get_bun_bun_js_files) + echo "$file"\t"Bun.js" + end + end +end + + +# Clear existing completions +complete -e -c bun + +# Dynamically emit scripts and binaries +complete -c bun -f -a "(__bun_complete_bins_scripts)" + +# Complete flags if we have no subcommand or a flag-friendly one. +set -l flag_applies "__fish_use_subcommand; or __fish_seen_subcommand_from $bun_builtin_cmds_accepting_flags" +complete -c bun \ + -n $flag_applies --no-files -s 'u' -l 'origin' -r -d 'Server URL. Rewrites import paths' +complete -c bun \ + -n $flag_applies --no-files -s 'p' -l 'port' -r -d 'Port number to start server from' +complete -c bun \ + -n $flag_applies --no-files -s 'd' -l 'define' -r -d 'Substitute K:V while parsing, e.g. --define process.env.NODE_ENV:\"development\"' +complete -c bun \ + -n $flag_applies --no-files -s 'e' -l 'external' -r -d 'Exclude module from transpilation (can use * wildcards). ex: -e react' +complete -c bun \ + -n $flag_applies --no-files -l 'use' -r -d 'Use a framework (ex: next)' +complete -c bun \ + -n $flag_applies --no-files -l 'hot' -r -d 'Enable hot reloading in Bun\'s JavaScript runtime' + +# Complete dev and create as first subcommand. +complete -c bun \ + -n "__fish_use_subcommand" -a 'dev' -d 'Start dev server' +complete -c bun \ + -n "__fish_use_subcommand" -a 'create' -f -d 'Create a new project from a template' + +# Complete "next" and "react" if we've seen "create". +complete -c bun \ + -n "__fish_seen_subcommand_from create" -a 'next' -d 'new Next.js project' + +complete -c bun \ + -n "__fish_seen_subcommand_from create" -a 'react' -d 'new React project' + +# Complete "upgrade" as first subcommand. +complete -c bun \ + -n "__fish_use_subcommand" -a 'upgrade' -d 'Upgrade bun to the latest version' -x +# Complete "-h/--help" unconditionally. +complete -c bun \ + -s "h" -l "help" -d 'See all commands and flags' -x + +# Complete "-v/--version" if we have no subcommand. +complete -c bun \ + -n "not __fish_use_subcommand" -l "version" -s "v" -d 'Bun\'s version' -x + +# Complete additional subcommands. +complete -c bun \ + -n "__fish_use_subcommand" -a 'discord' -d 'Open bun\'s Discord server' -x + + +complete -c bun \ + -n "__fish_use_subcommand" -a 'bun' -d 'Generate a new bundle' + + +complete -c bun \ + -n "__fish_seen_subcommand_from bun" -F -d 'Bundle this' + +complete -c bun \ + -n "__fish_seen_subcommand_from create; and __fish_seen_subcommand_from react next" -F -d "Create in directory" + + +complete -c bun \ + -n "__fish_use_subcommand" -a 'init' -F -d 'Start an empty Bun project' + +complete -c bun \ + -n "__fish_use_subcommand" -a 'install' -f -d 'Install packages from package.json' + +complete -c bun \ + -n "__fish_use_subcommand" -a 'add' -F -d 'Add a package to package.json' + +complete -c bun \ + -n "__fish_use_subcommand" -a 'remove' -F -d 'Remove a package from package.json' + + +for i in (seq (count $bun_install_boolean_flags)) + complete -c bun \ + -n "__fish_seen_subcommand_from install add remove update" -l "$bun_install_boolean_flags[$i]" -d "$bun_install_boolean_flags_descriptions[$i]" +end + +complete -c bun \ + -n "__fish_seen_subcommand_from install add remove update" -l 'cwd' -d 'Change working directory' + +complete -c bun \ + -n "__fish_seen_subcommand_from install add remove update" -l 'cache-dir' -d 'Choose a cache directory (default: $HOME/.bun/install/cache)' + +complete -c bun \ + -n "__fish_seen_subcommand_from add" -d 'Popular' -a '(__fish__get_bun_packages)' + +complete -c bun \ + -n "__fish_seen_subcommand_from add" -d 'History' -a '(__history_completions)' + +complete -c bun \ + -n "__fish_seen_subcommand_from pm; and not __fish_seen_subcommand_from (__fish__get_bun_bins) (__fish__get_bun_scripts) cache;" -a 'bin ls cache hash hash-print hash-string' -f + +complete -c bun \ + -n "__fish_seen_subcommand_from pm; and __fish_seen_subcommand_from cache; and not __fish_seen_subcommand_from (__fish__get_bun_bins) (__fish__get_bun_scripts);" -a 'rm' -f + +# Add built-in subcommands with descriptions. +complete -c bun -n "__fish_use_subcommand" -a "create" -f -d "Create a new project from a template" +complete -c bun -n "__fish_use_subcommand" -a "build bun" --require-parameter -F -d "Transpile and bundle one or more files" +complete -c bun -n "__fish_use_subcommand" -a "upgrade" -d "Upgrade Bun" +complete -c bun -n "__fish_use_subcommand" -a "run" -d "Run a script or package binary" +complete -c bun -n "__fish_use_subcommand" -a "install" -d "Install dependencies from package.json" -f +complete -c bun -n "__fish_use_subcommand" -a "remove" -d "Remove a dependency from package.json" -f +complete -c bun -n "__fish_use_subcommand" -a "add" -d "Add a dependency to package.json" -f +complete -c bun -n "__fish_use_subcommand" -a "init" -d "Initialize a Bun project in this directory" -f +complete -c bun -n "__fish_use_subcommand" -a "link" -d "Register or link a local npm package" -f +complete -c bun -n "__fish_use_subcommand" -a "unlink" -d "Unregister a local npm package" -f +complete -c bun -n "__fish_use_subcommand" -a "pm" -d "Additional package management utilities" -f +complete -c bun -n "__fish_use_subcommand" -a "x" -d "Execute a package binary, installing if needed" -f +complete -c bun -n "__fish_use_subcommand" -a "outdated" -d "Display the latest versions of outdated dependencies" -f +complete -c bun -n "__fish_use_subcommand" -a "update" -d "Update dependencies to their latest versions" -f +complete -c bun -n "__fish_use_subcommand" -a "publish" -d "Publish your package from local to npm" -f +complete -c bun -n "__fish_use_subcommand" -a "repl" -d "Start a REPL session with Bun" -f +complete -c bun -n "__fish_seen_subcommand_from repl" -s "e" -l "eval" -r -d "Evaluate argument as a script, then exit" -f +complete -c bun -n "__fish_seen_subcommand_from repl" -s "p" -l "print" -r -d "Evaluate argument as a script, print the result, then exit" -f +complete -c bun -n "__fish_seen_subcommand_from repl" -s "r" -l "preload" -r -d "Import a module before other modules are loaded" +complete -c bun -n "__fish_seen_subcommand_from repl" -l "smol" -d "Use less memory, but run garbage collection more often" -f +complete -c bun -n "__fish_seen_subcommand_from repl" -s "c" -l "config" -r -d "Specify path to Bun config file" +complete -c bun -n "__fish_seen_subcommand_from repl" -l "cwd" -r -d "Absolute path to resolve files & entry points from" +complete -c bun -n "__fish_seen_subcommand_from repl" -l "env-file" -r -d "Load environment variables from the specified file(s)" +complete -c bun -n "__fish_seen_subcommand_from repl" -l "no-env-file" -d "Disable automatic loading of .env files" -f diff --git a/reference/home/.config/fish/completions/dot.fish b/reference/home/.config/fish/completions/dot.fish new file mode 100644 index 0000000..d686b0b --- /dev/null +++ b/reference/home/.config/fish/completions/dot.fish @@ -0,0 +1,34 @@ +function __dot_custom_subcommands + echo init + echo help + path basename $HOME/.config/dot/commands/*.fish 2>/dev/null | path change-extension '' + + for d in $HOME/.config/dot/commands/*/ + test -d $d; or continue + set -l name (path basename $d) + test -f $d$name.fish; or continue + echo $name + end +end + +complete -c dot -n __fish_use_subcommand -a "(__dot_custom_subcommands)" + +# --- dot install --- +complete -c dot -n "__fish_seen_subcommand_from install; and not __fish_seen_argument -l restore" -l restore -d "reinstall every package from the saved list" +complete -c dot -n "__fish_seen_subcommand_from install; and not __fish_seen_argument -l no-sync" -l no-sync -d "skip the pacman -Sy database refresh" +complete -c dot -n "__fish_seen_subcommand_from install; and not __fish_seen_argument -l restore" -f -a "(__fish_print_pacman_packages)" + +# --- dot setup --- +complete -c dot -n "__fish_seen_subcommand_from setup; and not __fish_seen_subcommand_from folders help" -f -a folders -d "bring the 8 standard XDG user directories under the short-name convention" +complete -c dot -n "__fish_seen_subcommand_from setup; and not __fish_seen_subcommand_from folders help" -f -a help -d "show usage" +complete -c dot -n "__fish_seen_subcommand_from setup; and __fish_seen_subcommand_from folders" -f -a help -d "show usage" + +# --- dot kde --- +complete -c dot -n "__fish_seen_subcommand_from kde; and not __fish_seen_subcommand_from apply diff save help" -f -a apply -d "push manifest entries onto the live system" +complete -c dot -n "__fish_seen_subcommand_from kde; and not __fish_seen_subcommand_from apply diff save help" -f -a diff -d "scan for settings whose live value differs from its default" +complete -c dot -n "__fish_seen_subcommand_from kde; and not __fish_seen_subcommand_from apply diff save help" -f -a save -d "write live KDE settings into the manifest" +complete -c dot -n "__fish_seen_subcommand_from kde; and not __fish_seen_subcommand_from apply diff save help" -f -a help -d "show usage" +complete -c dot -n "__fish_seen_subcommand_from kde; and __fish_seen_subcommand_from apply diff save" -f -a help -d "show usage" +# Sourced live from the schema mapping table (real .kcfg files), not a +# hardcoded list -- same helper kde.py's own save/refresh logic builds from. +complete -c dot -n "__fish_seen_subcommand_from kde; and __fish_seen_subcommand_from save" -f -a "(python3 $HOME/.config/dot/commands/kde/kde.py complete 2>/dev/null)" diff --git a/reference/home/.config/fish/completions/fishtape.fish b/reference/home/.config/fish/completions/fishtape.fish new file mode 100644 index 0000000..ad81efe --- /dev/null +++ b/reference/home/.config/fish/completions/fishtape.fish @@ -0,0 +1,2 @@ +complete --command fishtape --short v --long version --description "Print version" +complete --command fishtape --short h --long help --description "Print help" diff --git a/reference/home/.config/fish/conf.d/aliases.fish b/reference/home/.config/fish/conf.d/aliases.fish new file mode 100644 index 0000000..a0e0c45 --- /dev/null +++ b/reference/home/.config/fish/conf.d/aliases.fish @@ -0,0 +1,4 @@ +alias cp='cp -v' +alias vi=nvim +alias vim=nvim +alias tmx='tmux new-session -A -s' diff --git a/reference/home/.config/fish/conf.d/env.fish b/reference/home/.config/fish/conf.d/env.fish new file mode 100644 index 0000000..ceeae7e --- /dev/null +++ b/reference/home/.config/fish/conf.d/env.fish @@ -0,0 +1,4 @@ +set -gx EDITOR nvim +set -x ANDROID_HOME $HOME/Android/Sdk +fish_add_path $ANDROID_HOME/platform-tools +fish_add_path $ANDROID_HOME/tools/bin diff --git a/reference/home/.config/fish/conf.d/rustup.fish b/reference/home/.config/fish/conf.d/rustup.fish new file mode 100644 index 0000000..e4cb363 --- /dev/null +++ b/reference/home/.config/fish/conf.d/rustup.fish @@ -0,0 +1 @@ +source "$HOME/.cargo/env.fish" diff --git a/reference/home/.config/fish/config.fish b/reference/home/.config/fish/config.fish new file mode 100644 index 0000000..a3741f1 --- /dev/null +++ b/reference/home/.config/fish/config.fish @@ -0,0 +1,17 @@ +source /usr/share/cachyos-fish-config/cachyos-config.fish + +set -gx EDITOR nvim +set -gx VISUAL nvim + +# overwrite greeting +# potentially disabling fastfetch +#function fish_greeting +# # smth smth +#end + +# bun +set --export BUN_INSTALL "$HOME/.bun" +set --export PATH $BUN_INSTALL/bin $PATH + +# Pi +fish_add_path "/home/alexion/.local/opt/node-v24.18.0-linux-x64/bin" diff --git a/reference/home/.config/fish/fish_plugins b/reference/home/.config/fish/fish_plugins new file mode 100644 index 0000000..32526b8 --- /dev/null +++ b/reference/home/.config/fish/fish_plugins @@ -0,0 +1 @@ +jorgebucaran/fishtape diff --git a/reference/home/.config/fish/functions/dot.fish b/reference/home/.config/fish/functions/dot.fish new file mode 100644 index 0000000..aea4c0d --- /dev/null +++ b/reference/home/.config/fish/functions/dot.fish @@ -0,0 +1,130 @@ +function dot --wraps=git --description 'Manage dotfiles via a bare repo checked out over $HOME' + set -l dotfiles_dir $HOME/.dotfiles + + if test "$argv[1]" = init + set -e argv[1] + __dot_init $dotfiles_dir $argv + return $status + end + + if test "$argv[1]" = help + __dot_help + return $status + end + + set -l commands_dir $HOME/.config/dot/commands + set -l command_file $commands_dir/$argv[1].fish + set -l nested_command_file $commands_dir/$argv[1]/$argv[1].fish + + if test -n "$argv[1]" + if test -f "$command_file" + source $command_file + _dot_$argv[1] $argv[2..-1] + return $status + else if test -f "$nested_command_file" + source $nested_command_file + _dot_$argv[1] $argv[2..-1] + return $status + end + end + + git --git-dir=$dotfiles_dir --work-tree=$HOME $argv +end + +# Kept inline (not a separate autoloaded function file) because this is the +# only subcommand that must work before the dotfiles repo has been cloned. +function __dot_init + set -l dotfiles_dir $argv[1] + set -e argv[1] + + argparse 'url=' -- $argv + or return 1 + + set -l url $_flag_url + test -n "$url"; or set url ssh://gitea@git.alexion.dev:2022/alexion/dotfiles.git + + if test -e $dotfiles_dir + echo "dot init: $dotfiles_dir already exists, refusing to re-initialize" >&2 + return 1 + end + + git clone --bare $url $dotfiles_dir + or begin + echo "dot init: failed to clone $url" >&2 + return 1 + end + + git --git-dir=$dotfiles_dir config status.showUntrackedFiles no + + set -l checkout_output (git --git-dir=$dotfiles_dir --work-tree=$HOME checkout 2>&1) + set -l checkout_status $status + + if test $checkout_status -ne 0 + set -l conflicts + set -l in_block 0 + + for line in $checkout_output + if test $in_block -eq 1 + if string match -rq '^\s' -- $line + set -a conflicts (string trim -- $line) + continue + else + set in_block 0 + end + end + + string match -q '*would be overwritten by checkout:*' -- $line + and set in_block 1 + end + + if test (count $conflicts) -eq 0 + echo "dot init: checkout failed and no recoverable conflicts were found:" >&2 + printf '%s\n' $checkout_output >&2 + return 1 + end + + set -l backup_dir $HOME/.dotfiles-backup/(date +%Y%m%dT%H%M%S) + for f in $conflicts + mkdir -p (path dirname $backup_dir/$f) + mv $HOME/$f $backup_dir/$f + echo "dot init: backed up ~/$f to $backup_dir/$f" + end + + git --git-dir=$dotfiles_dir --work-tree=$HOME checkout + or begin + echo "dot init: checkout still failing after backing up conflicts, aborting" >&2 + return 1 + end + end + + echo "dot init: bootstrapped $dotfiles_dir from $url" +end + +# The custom-subcommand glob is duplicated (not shared with +# completions/dot.fish) because fish only autoloads a function from a file +# named after that function; a shared helper would go undefined if `dot help` +# ran in a completion context before `dot` itself had ever been sourced. +function __dot_help + echo "dot: manage dotfiles via a bare repo checked out over \$HOME + +Commands: + init bootstrap the dotfiles repo on a new machine + help show this message" + + for f in $HOME/.config/dot/commands/*.fish + test -e $f; or continue + echo " "(path basename $f | path change-extension '') + end + + for d in $HOME/.config/dot/commands/*/ + test -d $d; or continue + set -l name (path basename $d) + test -f $d$name.fish; or continue + echo " $name" + end + + echo " +Run 'dot help' for flags on a specific command. + +Any other command is passed through to git (dot status, dot add, dot commit, dot push, ...)." +end diff --git a/reference/home/.config/fish/functions/fishtape.fish b/reference/home/.config/fish/functions/fishtape.fish new file mode 100644 index 0000000..7759f92 --- /dev/null +++ b/reference/home/.config/fish/functions/fishtape.fish @@ -0,0 +1,116 @@ +function fishtape --description "Test scripts, functions, and plugins in Fish" + switch "$argv" + case -v --version + echo "fishtape, version 3.0.1" + case "" -h --help + echo "Usage: fishtape Run test files" + echo "Options:" + echo " -v or --version Print version" + echo " -h or --help Print this help message" + case \* + set --local files (realpath $argv) + + for file in $files + if test ! -f $file + echo "fishtape: Invalid file or file not found: \"$file\"" >&2 + return 1 + end + end + + set --local operators -{n,z,b,c,d,e,f,g,G,k,L,O,p,r,s,S,t,u,w,x} + set --local expectations \ + "a non-zero length string" \ + "a zero length string" \ + "a block device" \ + "a character device" \ + "a directory" \ + "an existing file" \ + "a regular file" \ + "a file with the set-group-ID bit set" \ + "a file with same group ID as the current user" \ + "a file with the sticky bit set" \ + "a symbolic link" \ + "a file owned by the current user" \ + "a named pipe" \ + "a file marked as readable" \ + "a file of size greater than zero" \ + "a socket" \ + "a terminal tty file descriptor" \ + "a file with the set-user-ID bit set" \ + "a file marked as writable" \ + "a file marked as executable" + + set --universal _fishtape_test_number 0 + set --universal _fishtape_test_passed 0 + set --universal _fishtape_test_failed 0 + + function @echo + echo "# $argv" + end + + function @test --argument-names name --inherit-variable operators --inherit-variable expectations + set --erase argv[1] + set --query argv[2] || set --append argv "" + + set _fishtape_test_number (math $_fishtape_test_number + 1) + + if test $argv + set _fishtape_test_passed (math $_fishtape_test_passed + 1) + + echo "ok $_fishtape_test_number $name" + else + if test $argv[1] = "!" + set operator "! " + set expected "not " + set --erase argv[1] + end + + if set --query argv[3] + set operator "$operator"$argv[2] + set expected (string escape -- $argv[3]) + set actual (string escape -- $argv[1]) + else + set operator "$operator"$argv[1] + set expected "$expected"$expectations[(contains --index -- $argv[1] $operators)] + set actual (string escape -- $argv[2]) + end + + set _fishtape_test_failed (math $_fishtape_test_failed + 1) + + status print-stack-trace | + string replace --filter --regex -- "\s+called on line (\d+) of file (.+)" '$2:$1' | + read --local at + + echo "not ok $_fishtape_test_number $name" + echo " ---" + echo " operator: $operator" + echo " expected: $expected" + echo " actual: $actual" + echo " at: $at" + echo " ..." + end + end + + echo TAP version 13 + + for file in $files + fish --init-command=(functions @echo | string collect) --init-command=(functions @test | string collect) $file + end + + echo + echo "1..$_fishtape_test_number" + echo "# pass $_fishtape_test_passed" + test $_fishtape_test_failed -eq 0 && + echo "# ok" || + echo "# fail $_fishtape_test_failed" + + functions --erase @echo @test + + set --local failed $_fishtape_test_failed + set --erase _fishtape_test_number + set --erase _fishtape_test_passed + set --erase _fishtape_test_failed + + test $failed -eq 0 + end +end diff --git a/reference/home/.config/fontconfig/fonts.conf b/reference/home/.config/fontconfig/fonts.conf new file mode 100644 index 0000000..acd04e3 --- /dev/null +++ b/reference/home/.config/fontconfig/fonts.conf @@ -0,0 +1,79 @@ + + + + + + + + roman + + + + roman + + + + + matrix + + 1 + 0.2 + 0 + 1 + + + + + + oblique + + + + false + + + + + + + medium + + + + bold + + + + true + + + + bold + + + + + true + + + + + hintslight + + + + + true + + + diff --git a/reference/home/.config/gtk-3.0/assets/close-active.svg b/reference/home/.config/gtk-3.0/assets/close-active.svg new file mode 100644 index 0000000..8c5c4ae --- /dev/null +++ b/reference/home/.config/gtk-3.0/assets/close-active.svg @@ -0,0 +1,33 @@ + + +Qt SVG Document +Generated with Qt + + + + + + + + + + + + + + + + + + + + diff --git a/reference/home/.config/gtk-3.0/assets/close-backdrop-active.svg b/reference/home/.config/gtk-3.0/assets/close-backdrop-active.svg new file mode 100644 index 0000000..97b61cd --- /dev/null +++ b/reference/home/.config/gtk-3.0/assets/close-backdrop-active.svg @@ -0,0 +1,33 @@ + + +Qt SVG Document +Generated with Qt + + + + + + + + + + + + + + + + + + + + diff --git a/reference/home/.config/gtk-3.0/assets/close-backdrop-hover.svg b/reference/home/.config/gtk-3.0/assets/close-backdrop-hover.svg new file mode 100644 index 0000000..fff82c1 --- /dev/null +++ b/reference/home/.config/gtk-3.0/assets/close-backdrop-hover.svg @@ -0,0 +1,33 @@ + + +Qt SVG Document +Generated with Qt + + + + + + + + + + + + + + + + + + + + diff --git a/reference/home/.config/gtk-3.0/assets/close-backdrop-normal.svg b/reference/home/.config/gtk-3.0/assets/close-backdrop-normal.svg new file mode 100644 index 0000000..686fc75 --- /dev/null +++ b/reference/home/.config/gtk-3.0/assets/close-backdrop-normal.svg @@ -0,0 +1,27 @@ + + +Qt SVG Document +Generated with Qt + + + + + + + + + + + + + + + + diff --git a/reference/home/.config/gtk-3.0/assets/close-hover.svg b/reference/home/.config/gtk-3.0/assets/close-hover.svg new file mode 100644 index 0000000..fa11728 --- /dev/null +++ b/reference/home/.config/gtk-3.0/assets/close-hover.svg @@ -0,0 +1,33 @@ + + +Qt SVG Document +Generated with Qt + + + + + + + + + + + + + + + + + + + + diff --git a/reference/home/.config/gtk-3.0/assets/close-normal.svg b/reference/home/.config/gtk-3.0/assets/close-normal.svg new file mode 100644 index 0000000..c9b8281 --- /dev/null +++ b/reference/home/.config/gtk-3.0/assets/close-normal.svg @@ -0,0 +1,27 @@ + + +Qt SVG Document +Generated with Qt + + + + + + + + + + + + + + + + diff --git a/reference/home/.config/gtk-3.0/assets/maximize-active.svg b/reference/home/.config/gtk-3.0/assets/maximize-active.svg new file mode 100644 index 0000000..2cb231c --- /dev/null +++ b/reference/home/.config/gtk-3.0/assets/maximize-active.svg @@ -0,0 +1,32 @@ + + +Qt SVG Document +Generated with Qt + + + + + + + + + + + + + + + + + + + diff --git a/reference/home/.config/gtk-3.0/assets/maximize-backdrop-active.svg b/reference/home/.config/gtk-3.0/assets/maximize-backdrop-active.svg new file mode 100644 index 0000000..87452e9 --- /dev/null +++ b/reference/home/.config/gtk-3.0/assets/maximize-backdrop-active.svg @@ -0,0 +1,32 @@ + + +Qt SVG Document +Generated with Qt + + + + + + + + + + + + + + + + + + + diff --git a/reference/home/.config/gtk-3.0/assets/maximize-backdrop-hover.svg b/reference/home/.config/gtk-3.0/assets/maximize-backdrop-hover.svg new file mode 100644 index 0000000..41112d5 --- /dev/null +++ b/reference/home/.config/gtk-3.0/assets/maximize-backdrop-hover.svg @@ -0,0 +1,32 @@ + + +Qt SVG Document +Generated with Qt + + + + + + + + + + + + + + + + + + + diff --git a/reference/home/.config/gtk-3.0/assets/maximize-backdrop-normal.svg b/reference/home/.config/gtk-3.0/assets/maximize-backdrop-normal.svg new file mode 100644 index 0000000..2022b73 --- /dev/null +++ b/reference/home/.config/gtk-3.0/assets/maximize-backdrop-normal.svg @@ -0,0 +1,26 @@ + + +Qt SVG Document +Generated with Qt + + + + + + + + + + + + + + + diff --git a/reference/home/.config/gtk-3.0/assets/maximize-hover.svg b/reference/home/.config/gtk-3.0/assets/maximize-hover.svg new file mode 100644 index 0000000..e185b9b --- /dev/null +++ b/reference/home/.config/gtk-3.0/assets/maximize-hover.svg @@ -0,0 +1,32 @@ + + +Qt SVG Document +Generated with Qt + + + + + + + + + + + + + + + + + + + diff --git a/reference/home/.config/gtk-3.0/assets/maximize-normal.svg b/reference/home/.config/gtk-3.0/assets/maximize-normal.svg new file mode 100644 index 0000000..801997e --- /dev/null +++ b/reference/home/.config/gtk-3.0/assets/maximize-normal.svg @@ -0,0 +1,26 @@ + + +Qt SVG Document +Generated with Qt + + + + + + + + + + + + + + + diff --git a/reference/home/.config/gtk-3.0/assets/maximized-active.svg b/reference/home/.config/gtk-3.0/assets/maximized-active.svg new file mode 100644 index 0000000..0d94719 --- /dev/null +++ b/reference/home/.config/gtk-3.0/assets/maximized-active.svg @@ -0,0 +1,32 @@ + + +Qt SVG Document +Generated with Qt + + + + + + + + + + + + + + + + + + + diff --git a/reference/home/.config/gtk-3.0/assets/maximized-backdrop-active.svg b/reference/home/.config/gtk-3.0/assets/maximized-backdrop-active.svg new file mode 100644 index 0000000..2ab39cf --- /dev/null +++ b/reference/home/.config/gtk-3.0/assets/maximized-backdrop-active.svg @@ -0,0 +1,32 @@ + + +Qt SVG Document +Generated with Qt + + + + + + + + + + + + + + + + + + + diff --git a/reference/home/.config/gtk-3.0/assets/maximized-backdrop-hover.svg b/reference/home/.config/gtk-3.0/assets/maximized-backdrop-hover.svg new file mode 100644 index 0000000..6ed6b12 --- /dev/null +++ b/reference/home/.config/gtk-3.0/assets/maximized-backdrop-hover.svg @@ -0,0 +1,32 @@ + + +Qt SVG Document +Generated with Qt + + + + + + + + + + + + + + + + + + + diff --git a/reference/home/.config/gtk-3.0/assets/maximized-backdrop-normal.svg b/reference/home/.config/gtk-3.0/assets/maximized-backdrop-normal.svg new file mode 100644 index 0000000..2e3594f --- /dev/null +++ b/reference/home/.config/gtk-3.0/assets/maximized-backdrop-normal.svg @@ -0,0 +1,26 @@ + + +Qt SVG Document +Generated with Qt + + + + + + + + + + + + + + + diff --git a/reference/home/.config/gtk-3.0/assets/maximized-hover.svg b/reference/home/.config/gtk-3.0/assets/maximized-hover.svg new file mode 100644 index 0000000..09e39ea --- /dev/null +++ b/reference/home/.config/gtk-3.0/assets/maximized-hover.svg @@ -0,0 +1,32 @@ + + +Qt SVG Document +Generated with Qt + + + + + + + + + + + + + + + + + + + diff --git a/reference/home/.config/gtk-3.0/assets/maximized-normal.svg b/reference/home/.config/gtk-3.0/assets/maximized-normal.svg new file mode 100644 index 0000000..bf23e18 --- /dev/null +++ b/reference/home/.config/gtk-3.0/assets/maximized-normal.svg @@ -0,0 +1,26 @@ + + +Qt SVG Document +Generated with Qt + + + + + + + + + + + + + + + diff --git a/reference/home/.config/gtk-3.0/assets/minimize-active.svg b/reference/home/.config/gtk-3.0/assets/minimize-active.svg new file mode 100644 index 0000000..e5835d0 --- /dev/null +++ b/reference/home/.config/gtk-3.0/assets/minimize-active.svg @@ -0,0 +1,32 @@ + + +Qt SVG Document +Generated with Qt + + + + + + + + + + + + + + + + + + + diff --git a/reference/home/.config/gtk-3.0/assets/minimize-backdrop-active.svg b/reference/home/.config/gtk-3.0/assets/minimize-backdrop-active.svg new file mode 100644 index 0000000..5e0b1b7 --- /dev/null +++ b/reference/home/.config/gtk-3.0/assets/minimize-backdrop-active.svg @@ -0,0 +1,32 @@ + + +Qt SVG Document +Generated with Qt + + + + + + + + + + + + + + + + + + + diff --git a/reference/home/.config/gtk-3.0/assets/minimize-backdrop-hover.svg b/reference/home/.config/gtk-3.0/assets/minimize-backdrop-hover.svg new file mode 100644 index 0000000..761d383 --- /dev/null +++ b/reference/home/.config/gtk-3.0/assets/minimize-backdrop-hover.svg @@ -0,0 +1,32 @@ + + +Qt SVG Document +Generated with Qt + + + + + + + + + + + + + + + + + + + diff --git a/reference/home/.config/gtk-3.0/assets/minimize-backdrop-normal.svg b/reference/home/.config/gtk-3.0/assets/minimize-backdrop-normal.svg new file mode 100644 index 0000000..93226c9 --- /dev/null +++ b/reference/home/.config/gtk-3.0/assets/minimize-backdrop-normal.svg @@ -0,0 +1,26 @@ + + +Qt SVG Document +Generated with Qt + + + + + + + + + + + + + + + diff --git a/reference/home/.config/gtk-3.0/assets/minimize-hover.svg b/reference/home/.config/gtk-3.0/assets/minimize-hover.svg new file mode 100644 index 0000000..46853fc --- /dev/null +++ b/reference/home/.config/gtk-3.0/assets/minimize-hover.svg @@ -0,0 +1,32 @@ + + +Qt SVG Document +Generated with Qt + + + + + + + + + + + + + + + + + + + diff --git a/reference/home/.config/gtk-3.0/assets/minimize-normal.svg b/reference/home/.config/gtk-3.0/assets/minimize-normal.svg new file mode 100644 index 0000000..8034b01 --- /dev/null +++ b/reference/home/.config/gtk-3.0/assets/minimize-normal.svg @@ -0,0 +1,26 @@ + + +Qt SVG Document +Generated with Qt + + + + + + + + + + + + + + + diff --git a/reference/home/.config/gtk-3.0/colors.css b/reference/home/.config/gtk-3.0/colors.css new file mode 100644 index 0000000..325eb22 --- /dev/null +++ b/reference/home/.config/gtk-3.0/colors.css @@ -0,0 +1,84 @@ +@define-color borders_breeze #4c4e51; +@define-color content_view_bg_breeze #141618; +@define-color error_color_backdrop_breeze #da4453; +@define-color error_color_breeze #da4453; +@define-color error_color_insensitive_backdrop_breeze #55242b; +@define-color error_color_insensitive_breeze #55242b; +@define-color insensitive_base_color_breeze #131517; +@define-color insensitive_base_fg_color_breeze #606263; +@define-color insensitive_bg_color_breeze #1f2124; +@define-color insensitive_borders_breeze #2d3033; +@define-color insensitive_fg_color_breeze #686a6c; +@define-color insensitive_selected_bg_color_breeze #1f2124; +@define-color insensitive_selected_fg_color_breeze #686a6c; +@define-color insensitive_unfocused_bg_color_breeze #1f2124; +@define-color insensitive_unfocused_fg_color_breeze #686a6c; +@define-color insensitive_unfocused_selected_bg_color_breeze #1f2124; +@define-color insensitive_unfocused_selected_fg_color_breeze #686a6c; +@define-color link_color_breeze #1d99f3; +@define-color link_visited_color_breeze #9b59b6; +@define-color success_color_backdrop_breeze #27ae60; +@define-color success_color_breeze #27ae60; +@define-color success_color_insensitive_backdrop_breeze #19482f; +@define-color success_color_insensitive_breeze #19482f; +@define-color theme_base_color_breeze #141618; +@define-color theme_bg_color_breeze #202326; +@define-color theme_button_background_backdrop_breeze #292c30; +@define-color theme_button_background_backdrop_insensitive_breeze #272a2e; +@define-color theme_button_background_insensitive_breeze #272a2e; +@define-color theme_button_background_normal_breeze #292c30; +@define-color theme_button_decoration_focus_backdrop_breeze #3daee9; +@define-color theme_button_decoration_focus_backdrop_insensitive_breeze #2e556b; +@define-color theme_button_decoration_focus_breeze #3daee9; +@define-color theme_button_decoration_focus_insensitive_breeze #2e556b; +@define-color theme_button_decoration_hover_backdrop_breeze #3daee9; +@define-color theme_button_decoration_hover_backdrop_insensitive_breeze #2e556b; +@define-color theme_button_decoration_hover_breeze #3daee9; +@define-color theme_button_decoration_hover_insensitive_breeze #2e556b; +@define-color theme_button_foreground_active_backdrop_breeze #fcfcfc; +@define-color theme_button_foreground_active_backdrop_insensitive_breeze #686a6c; +@define-color theme_button_foreground_active_breeze #fcfcfc; +@define-color theme_button_foreground_active_insensitive_breeze #686a6c; +@define-color theme_button_foreground_backdrop_breeze #fcfcfc; +@define-color theme_button_foreground_backdrop_insensitive_breeze #6d6f72; +@define-color theme_button_foreground_insensitive_breeze #6d6f72; +@define-color theme_button_foreground_normal_breeze #fcfcfc; +@define-color theme_fg_color_breeze #fcfcfc; +@define-color theme_header_background_backdrop_breeze #202326; +@define-color theme_header_background_breeze #292c30; +@define-color theme_header_background_light_breeze #202326; +@define-color theme_header_foreground_backdrop_breeze #fcfcfc; +@define-color theme_header_foreground_breeze #fcfcfc; +@define-color theme_header_foreground_insensitive_backdrop_breeze #fcfcfc; +@define-color theme_header_foreground_insensitive_breeze #fcfcfc; +@define-color theme_hovering_selected_bg_color_breeze #3daee9; +@define-color theme_selected_bg_color_breeze #3daee9; +@define-color theme_selected_fg_color_breeze #fcfcfc; +@define-color theme_text_color_breeze #fcfcfc; +@define-color theme_titlebar_background_backdrop_breeze #202326; +@define-color theme_titlebar_background_breeze #292c30; +@define-color theme_titlebar_background_light_breeze #202326; +@define-color theme_titlebar_foreground_backdrop_breeze #fcfcfc; +@define-color theme_titlebar_foreground_breeze #fcfcfc; +@define-color theme_titlebar_foreground_insensitive_backdrop_breeze #fcfcfc; +@define-color theme_titlebar_foreground_insensitive_breeze #fcfcfc; +@define-color theme_unfocused_base_color_breeze #141618; +@define-color theme_unfocused_bg_color_breeze #202326; +@define-color theme_unfocused_fg_color_breeze #fcfcfc; +@define-color theme_unfocused_selected_bg_color_alt_breeze #1b4155; +@define-color theme_unfocused_selected_bg_color_breeze #1b4155; +@define-color theme_unfocused_selected_fg_color_breeze #fcfcfc; +@define-color theme_unfocused_text_color_breeze #fcfcfc; +@define-color theme_unfocused_view_bg_color_breeze #131517; +@define-color theme_unfocused_view_text_color_breeze #606263; +@define-color theme_view_active_decoration_color_breeze #3daee9; +@define-color theme_view_hover_decoration_color_breeze #3daee9; +@define-color tooltip_background_breeze #292c30; +@define-color tooltip_border_breeze #535659; +@define-color tooltip_text_breeze #fcfcfc; +@define-color unfocused_borders_breeze #4c4e51; +@define-color unfocused_insensitive_borders_breeze #2d3033; +@define-color warning_color_backdrop_breeze #f67400; +@define-color warning_color_breeze #f67400; +@define-color warning_color_insensitive_backdrop_breeze #5e340f; +@define-color warning_color_insensitive_breeze #5e340f; diff --git a/reference/home/.config/gtk-3.0/gtk.css b/reference/home/.config/gtk-3.0/gtk.css new file mode 100644 index 0000000..c9763f7 --- /dev/null +++ b/reference/home/.config/gtk-3.0/gtk.css @@ -0,0 +1 @@ +@import 'colors.css'; \ No newline at end of file diff --git a/reference/home/.config/gtk-3.0/settings.ini b/reference/home/.config/gtk-3.0/settings.ini new file mode 100644 index 0000000..15264ab --- /dev/null +++ b/reference/home/.config/gtk-3.0/settings.ini @@ -0,0 +1,18 @@ +[Settings] +gtk-application-prefer-dark-theme=true +gtk-button-images=true +gtk-cursor-blink=true +gtk-cursor-blink-time=1000 +gtk-cursor-theme-name=breeze_cursors +gtk-cursor-theme-size=24 +gtk-decoration-layout=icon:minimize,maximize,close +gtk-enable-animations=true +gtk-font-name=Noto Sans, 14 +gtk-icon-theme-name=breeze-dark +gtk-menu-images=true +gtk-modules=colorreload-gtk-module:window-decorations-gtk-module +gtk-primary-button-warps-slider=true +gtk-sound-theme-name=ocean +gtk-theme-name=Breeze +gtk-toolbar-style=3 +gtk-xft-dpi=98304 diff --git a/reference/home/.config/gtk-3.0/window_decorations.css b/reference/home/.config/gtk-3.0/window_decorations.css new file mode 100644 index 0000000..ca70a8a --- /dev/null +++ b/reference/home/.config/gtk-3.0/window_decorations.css @@ -0,0 +1,71 @@ +headerbar button.titlebutton.close, .titlebar button.titlebutton.close { + background-image: url("assets/close-normal.svg"); } + +headerbar button.titlebutton.close:hover, .titlebar button.titlebutton.close:hover { + background-image: url("assets/close-hover.svg"); } + +headerbar button.titlebutton.close:active, .titlebar button.titlebutton.close:active { + background-image: url("assets/close-active.svg"); } + +headerbar button.titlebutton.close:backdrop, .titlebar button.titlebutton.close:backdrop { + background-image: url("assets/close-backdrop-normal.svg"); } + +headerbar button.titlebutton.close:backdrop:hover, .titlebar button.titlebutton.close:backdrop:hover { + background-image: url("assets/close-backdrop-hover.svg"); } + +headerbar button.titlebutton.close:backdrop:active, .titlebar button.titlebutton.close:backdrop:active { + background-image: url("assets/close-backdrop-active.svg"); } + +headerbar button.titlebutton.maximize, .titlebar button.titlebutton.maximize { + background-image: url("assets/maximize-normal.svg"); } + +headerbar button.titlebutton.maximize:hover, .titlebar button.titlebutton.maximize:hover { + background-image: url("assets/maximize-hover.svg"); } + +headerbar button.titlebutton.maximize:active, .titlebar button.titlebutton.maximize:active { + background-image: url("assets/maximize-active.svg"); } + +headerbar button.titlebutton.maximize:backdrop, .titlebar button.titlebutton.maximize:backdrop { + background-image: url("assets/maximize-backdrop-normal.svg"); } + +headerbar button.titlebutton.maximize:backdrop:hover, .titlebar button.titlebutton.maximize:backdrop:hover { + background-image: url("assets/maximize-backdrop-hover.svg"); } + +headerbar button.titlebutton.maximize:backdrop:active, .titlebar button.titlebutton.maximize:backdrop:active { + background-image: url("assets/maximize-backdrop-active.svg"); } + +headerbar button.titlebutton.minimize, .titlebar button.titlebutton.minimize { + background-image: url("assets/minimize-normal.svg"); } + +headerbar button.titlebutton.minimize:hover, .titlebar button.titlebutton.minimize:hover { + background-image: url("assets/minimize-hover.svg"); } + +headerbar button.titlebutton.minimize:active, .titlebar button.titlebutton.minimize:active { + background-image: url("assets/minimize-active.svg"); } + +headerbar button.titlebutton.minimize:backdrop, .titlebar button.titlebutton.minimize:backdrop { + background-image: url("assets/minimize-backdrop-normal.svg"); } + +headerbar button.titlebutton.minimize:backdrop:hover, .titlebar button.titlebutton.minimize:backdrop:hover { + background-image: url("assets/minimize-backdrop-hover.svg"); } + +headerbar button.titlebutton.minimize:backdrop:active, .titlebar button.titlebutton.minimize:backdrop:active { + background-image: url("assets/minimize-backdrop-active.svg"); } + +.maximized headerbar button.titlebutton.maximize, .maximized .titlebar button.titlebutton.maximize { + background-image: url("assets/maximized-normal.svg"); } + +.maximized headerbar button.titlebutton.maximize:hover, .maximized .titlebar button.titlebutton.maximize:hover { + background-image: url("assets/maximized-hover.svg"); } + +.maximized headerbar button.titlebutton.maximize:active, .maximized .titlebar button.titlebutton.maximize:active { + background-image: url("assets/maximized-active.svg"); } + +.maximized headerbar button.titlebutton.maximize:backdrop, .maximized .titlebar button.titlebutton.maximize:backdrop { + background-image: url("assets/maximized-backdrop-normal.svg"); } + +.maximized headerbar button.titlebutton.maximize:backdrop:hover, .maximized .titlebar button.titlebutton.maximize:backdrop:hover { + background-image: url("assets/maximized-backdrop-hover.svg"); } + +.maximized headerbar button.titlebutton.maximize:backdrop:active, .maximized .titlebar button.titlebutton.maximize:backdrop:active { + background-image: url("assets/maximized-backdrop-active.svg"); } diff --git a/reference/home/.config/gtk-4.0/colors.css b/reference/home/.config/gtk-4.0/colors.css new file mode 100644 index 0000000..325eb22 --- /dev/null +++ b/reference/home/.config/gtk-4.0/colors.css @@ -0,0 +1,84 @@ +@define-color borders_breeze #4c4e51; +@define-color content_view_bg_breeze #141618; +@define-color error_color_backdrop_breeze #da4453; +@define-color error_color_breeze #da4453; +@define-color error_color_insensitive_backdrop_breeze #55242b; +@define-color error_color_insensitive_breeze #55242b; +@define-color insensitive_base_color_breeze #131517; +@define-color insensitive_base_fg_color_breeze #606263; +@define-color insensitive_bg_color_breeze #1f2124; +@define-color insensitive_borders_breeze #2d3033; +@define-color insensitive_fg_color_breeze #686a6c; +@define-color insensitive_selected_bg_color_breeze #1f2124; +@define-color insensitive_selected_fg_color_breeze #686a6c; +@define-color insensitive_unfocused_bg_color_breeze #1f2124; +@define-color insensitive_unfocused_fg_color_breeze #686a6c; +@define-color insensitive_unfocused_selected_bg_color_breeze #1f2124; +@define-color insensitive_unfocused_selected_fg_color_breeze #686a6c; +@define-color link_color_breeze #1d99f3; +@define-color link_visited_color_breeze #9b59b6; +@define-color success_color_backdrop_breeze #27ae60; +@define-color success_color_breeze #27ae60; +@define-color success_color_insensitive_backdrop_breeze #19482f; +@define-color success_color_insensitive_breeze #19482f; +@define-color theme_base_color_breeze #141618; +@define-color theme_bg_color_breeze #202326; +@define-color theme_button_background_backdrop_breeze #292c30; +@define-color theme_button_background_backdrop_insensitive_breeze #272a2e; +@define-color theme_button_background_insensitive_breeze #272a2e; +@define-color theme_button_background_normal_breeze #292c30; +@define-color theme_button_decoration_focus_backdrop_breeze #3daee9; +@define-color theme_button_decoration_focus_backdrop_insensitive_breeze #2e556b; +@define-color theme_button_decoration_focus_breeze #3daee9; +@define-color theme_button_decoration_focus_insensitive_breeze #2e556b; +@define-color theme_button_decoration_hover_backdrop_breeze #3daee9; +@define-color theme_button_decoration_hover_backdrop_insensitive_breeze #2e556b; +@define-color theme_button_decoration_hover_breeze #3daee9; +@define-color theme_button_decoration_hover_insensitive_breeze #2e556b; +@define-color theme_button_foreground_active_backdrop_breeze #fcfcfc; +@define-color theme_button_foreground_active_backdrop_insensitive_breeze #686a6c; +@define-color theme_button_foreground_active_breeze #fcfcfc; +@define-color theme_button_foreground_active_insensitive_breeze #686a6c; +@define-color theme_button_foreground_backdrop_breeze #fcfcfc; +@define-color theme_button_foreground_backdrop_insensitive_breeze #6d6f72; +@define-color theme_button_foreground_insensitive_breeze #6d6f72; +@define-color theme_button_foreground_normal_breeze #fcfcfc; +@define-color theme_fg_color_breeze #fcfcfc; +@define-color theme_header_background_backdrop_breeze #202326; +@define-color theme_header_background_breeze #292c30; +@define-color theme_header_background_light_breeze #202326; +@define-color theme_header_foreground_backdrop_breeze #fcfcfc; +@define-color theme_header_foreground_breeze #fcfcfc; +@define-color theme_header_foreground_insensitive_backdrop_breeze #fcfcfc; +@define-color theme_header_foreground_insensitive_breeze #fcfcfc; +@define-color theme_hovering_selected_bg_color_breeze #3daee9; +@define-color theme_selected_bg_color_breeze #3daee9; +@define-color theme_selected_fg_color_breeze #fcfcfc; +@define-color theme_text_color_breeze #fcfcfc; +@define-color theme_titlebar_background_backdrop_breeze #202326; +@define-color theme_titlebar_background_breeze #292c30; +@define-color theme_titlebar_background_light_breeze #202326; +@define-color theme_titlebar_foreground_backdrop_breeze #fcfcfc; +@define-color theme_titlebar_foreground_breeze #fcfcfc; +@define-color theme_titlebar_foreground_insensitive_backdrop_breeze #fcfcfc; +@define-color theme_titlebar_foreground_insensitive_breeze #fcfcfc; +@define-color theme_unfocused_base_color_breeze #141618; +@define-color theme_unfocused_bg_color_breeze #202326; +@define-color theme_unfocused_fg_color_breeze #fcfcfc; +@define-color theme_unfocused_selected_bg_color_alt_breeze #1b4155; +@define-color theme_unfocused_selected_bg_color_breeze #1b4155; +@define-color theme_unfocused_selected_fg_color_breeze #fcfcfc; +@define-color theme_unfocused_text_color_breeze #fcfcfc; +@define-color theme_unfocused_view_bg_color_breeze #131517; +@define-color theme_unfocused_view_text_color_breeze #606263; +@define-color theme_view_active_decoration_color_breeze #3daee9; +@define-color theme_view_hover_decoration_color_breeze #3daee9; +@define-color tooltip_background_breeze #292c30; +@define-color tooltip_border_breeze #535659; +@define-color tooltip_text_breeze #fcfcfc; +@define-color unfocused_borders_breeze #4c4e51; +@define-color unfocused_insensitive_borders_breeze #2d3033; +@define-color warning_color_backdrop_breeze #f67400; +@define-color warning_color_breeze #f67400; +@define-color warning_color_insensitive_backdrop_breeze #5e340f; +@define-color warning_color_insensitive_breeze #5e340f; diff --git a/reference/home/.config/gtk-4.0/gtk.css b/reference/home/.config/gtk-4.0/gtk.css new file mode 100644 index 0000000..c9763f7 --- /dev/null +++ b/reference/home/.config/gtk-4.0/gtk.css @@ -0,0 +1 @@ +@import 'colors.css'; \ No newline at end of file diff --git a/reference/home/.config/gtk-4.0/settings.ini b/reference/home/.config/gtk-4.0/settings.ini new file mode 100644 index 0000000..b4bf85a --- /dev/null +++ b/reference/home/.config/gtk-4.0/settings.ini @@ -0,0 +1,13 @@ +[Settings] +gtk-application-prefer-dark-theme=true +gtk-cursor-blink=true +gtk-cursor-blink-time=1000 +gtk-cursor-theme-name=breeze_cursors +gtk-cursor-theme-size=24 +gtk-decoration-layout=icon:minimize,maximize,close +gtk-enable-animations=true +gtk-font-name=Noto Sans, 14 +gtk-icon-theme-name=breeze-dark +gtk-primary-button-warps-slider=true +gtk-sound-theme-name=ocean +gtk-xft-dpi=98304 diff --git a/reference/home/.config/gtk-4.0/window_decorations.css b/reference/home/.config/gtk-4.0/window_decorations.css new file mode 100644 index 0000000..ca70a8a --- /dev/null +++ b/reference/home/.config/gtk-4.0/window_decorations.css @@ -0,0 +1,71 @@ +headerbar button.titlebutton.close, .titlebar button.titlebutton.close { + background-image: url("assets/close-normal.svg"); } + +headerbar button.titlebutton.close:hover, .titlebar button.titlebutton.close:hover { + background-image: url("assets/close-hover.svg"); } + +headerbar button.titlebutton.close:active, .titlebar button.titlebutton.close:active { + background-image: url("assets/close-active.svg"); } + +headerbar button.titlebutton.close:backdrop, .titlebar button.titlebutton.close:backdrop { + background-image: url("assets/close-backdrop-normal.svg"); } + +headerbar button.titlebutton.close:backdrop:hover, .titlebar button.titlebutton.close:backdrop:hover { + background-image: url("assets/close-backdrop-hover.svg"); } + +headerbar button.titlebutton.close:backdrop:active, .titlebar button.titlebutton.close:backdrop:active { + background-image: url("assets/close-backdrop-active.svg"); } + +headerbar button.titlebutton.maximize, .titlebar button.titlebutton.maximize { + background-image: url("assets/maximize-normal.svg"); } + +headerbar button.titlebutton.maximize:hover, .titlebar button.titlebutton.maximize:hover { + background-image: url("assets/maximize-hover.svg"); } + +headerbar button.titlebutton.maximize:active, .titlebar button.titlebutton.maximize:active { + background-image: url("assets/maximize-active.svg"); } + +headerbar button.titlebutton.maximize:backdrop, .titlebar button.titlebutton.maximize:backdrop { + background-image: url("assets/maximize-backdrop-normal.svg"); } + +headerbar button.titlebutton.maximize:backdrop:hover, .titlebar button.titlebutton.maximize:backdrop:hover { + background-image: url("assets/maximize-backdrop-hover.svg"); } + +headerbar button.titlebutton.maximize:backdrop:active, .titlebar button.titlebutton.maximize:backdrop:active { + background-image: url("assets/maximize-backdrop-active.svg"); } + +headerbar button.titlebutton.minimize, .titlebar button.titlebutton.minimize { + background-image: url("assets/minimize-normal.svg"); } + +headerbar button.titlebutton.minimize:hover, .titlebar button.titlebutton.minimize:hover { + background-image: url("assets/minimize-hover.svg"); } + +headerbar button.titlebutton.minimize:active, .titlebar button.titlebutton.minimize:active { + background-image: url("assets/minimize-active.svg"); } + +headerbar button.titlebutton.minimize:backdrop, .titlebar button.titlebutton.minimize:backdrop { + background-image: url("assets/minimize-backdrop-normal.svg"); } + +headerbar button.titlebutton.minimize:backdrop:hover, .titlebar button.titlebutton.minimize:backdrop:hover { + background-image: url("assets/minimize-backdrop-hover.svg"); } + +headerbar button.titlebutton.minimize:backdrop:active, .titlebar button.titlebutton.minimize:backdrop:active { + background-image: url("assets/minimize-backdrop-active.svg"); } + +.maximized headerbar button.titlebutton.maximize, .maximized .titlebar button.titlebutton.maximize { + background-image: url("assets/maximized-normal.svg"); } + +.maximized headerbar button.titlebutton.maximize:hover, .maximized .titlebar button.titlebutton.maximize:hover { + background-image: url("assets/maximized-hover.svg"); } + +.maximized headerbar button.titlebutton.maximize:active, .maximized .titlebar button.titlebutton.maximize:active { + background-image: url("assets/maximized-active.svg"); } + +.maximized headerbar button.titlebutton.maximize:backdrop, .maximized .titlebar button.titlebutton.maximize:backdrop { + background-image: url("assets/maximized-backdrop-normal.svg"); } + +.maximized headerbar button.titlebutton.maximize:backdrop:hover, .maximized .titlebar button.titlebutton.maximize:backdrop:hover { + background-image: url("assets/maximized-backdrop-hover.svg"); } + +.maximized headerbar button.titlebutton.maximize:backdrop:active, .maximized .titlebar button.titlebutton.maximize:backdrop:active { + background-image: url("assets/maximized-backdrop-active.svg"); } diff --git a/reference/home/.config/gtkrc-2.0 b/reference/home/.config/gtkrc-2.0 new file mode 100644 index 0000000..638be81 --- /dev/null +++ b/reference/home/.config/gtkrc-2.0 @@ -0,0 +1,5 @@ +# created by KDE Plasma, Sun Jul 12 19:24:12 2026 +# + +gtk-alternative-button-order = 1 + diff --git a/reference/home/.config/kcminputrc b/reference/home/.config/kcminputrc new file mode 100644 index 0000000..ad1d422 --- /dev/null +++ b/reference/home/.config/kcminputrc @@ -0,0 +1,2 @@ +[Libinput][1739][30383][DELL08AF:00 06CB:76AF Touchpad] +ClickMethod=2 diff --git a/reference/home/.config/kdeglobals b/reference/home/.config/kdeglobals new file mode 100644 index 0000000..2f4bcb9 --- /dev/null +++ b/reference/home/.config/kdeglobals @@ -0,0 +1,177 @@ +[ColorEffects:Disabled] +ChangeSelectionColor= +Color=56,56,56 +ColorAmount=0 +ColorEffect=0 +ContrastAmount=0.65 +ContrastEffect=1 +Enable= +IntensityAmount=0.1 +IntensityEffect=2 + +[ColorEffects:Inactive] +ChangeSelectionColor=true +Color=112,111,110 +ColorAmount=0.025 +ColorEffect=2 +ContrastAmount=0.1 +ContrastEffect=2 +Enable=false +IntensityAmount=0 +IntensityEffect=0 + +[Colors:Button] +BackgroundAlternate=30,87,116 +BackgroundNormal=41,44,48 +DecorationFocus=61,174,233 +DecorationHover=61,174,233 +ForegroundActive=61,174,233 +ForegroundInactive=161,169,177 +ForegroundLink=29,153,243 +ForegroundNegative=218,68,83 +ForegroundNeutral=246,116,0 +ForegroundNormal=252,252,252 +ForegroundPositive=39,174,96 +ForegroundVisited=155,89,182 + +[Colors:Complementary] +BackgroundAlternate=30,87,116 +BackgroundNormal=32,35,38 +DecorationFocus=61,174,233 +DecorationHover=61,174,233 +ForegroundActive=61,174,233 +ForegroundInactive=161,169,177 +ForegroundLink=29,153,243 +ForegroundNegative=218,68,83 +ForegroundNeutral=246,116,0 +ForegroundNormal=252,252,252 +ForegroundPositive=39,174,96 +ForegroundVisited=155,89,182 + +[Colors:Header] +BackgroundAlternate=32,35,38 +BackgroundNormal=41,44,48 +DecorationFocus=61,174,233 +DecorationHover=61,174,233 +ForegroundActive=61,174,233 +ForegroundInactive=161,169,177 +ForegroundLink=29,153,243 +ForegroundNegative=218,68,83 +ForegroundNeutral=246,116,0 +ForegroundNormal=252,252,252 +ForegroundPositive=39,174,96 +ForegroundVisited=155,89,182 + +[Colors:Header][Inactive] +BackgroundAlternate=41,44,48 +BackgroundNormal=32,35,38 +DecorationFocus=61,174,233 +DecorationHover=61,174,233 +ForegroundActive=61,174,233 +ForegroundInactive=161,169,177 +ForegroundLink=29,153,243 +ForegroundNegative=218,68,83 +ForegroundNeutral=246,116,0 +ForegroundNormal=252,252,252 +ForegroundPositive=39,174,96 +ForegroundVisited=155,89,182 + +[Colors:Selection] +BackgroundAlternate=30,87,116 +BackgroundNormal=61,174,233 +DecorationFocus=61,174,233 +DecorationHover=61,174,233 +ForegroundActive=252,252,252 +ForegroundInactive=161,169,177 +ForegroundLink=253,188,75 +ForegroundNegative=176,55,69 +ForegroundNeutral=198,92,0 +ForegroundNormal=252,252,252 +ForegroundPositive=23,104,57 +ForegroundVisited=155,89,182 + +[Colors:Tooltip] +BackgroundAlternate=32,35,38 +BackgroundNormal=41,44,48 +DecorationFocus=61,174,233 +DecorationHover=61,174,233 +ForegroundActive=61,174,233 +ForegroundInactive=161,169,177 +ForegroundLink=29,153,243 +ForegroundNegative=218,68,83 +ForegroundNeutral=246,116,0 +ForegroundNormal=252,252,252 +ForegroundPositive=39,174,96 +ForegroundVisited=155,89,182 + +[Colors:View] +BackgroundAlternate=29,31,34 +BackgroundNormal=20,22,24 +DecorationFocus=61,174,233 +DecorationHover=61,174,233 +ForegroundActive=61,174,233 +ForegroundInactive=161,169,177 +ForegroundLink=29,153,243 +ForegroundNegative=218,68,83 +ForegroundNeutral=246,116,0 +ForegroundNormal=252,252,252 +ForegroundPositive=39,174,96 +ForegroundVisited=155,89,182 + +[Colors:Window] +BackgroundAlternate=41,44,48 +BackgroundNormal=32,35,38 +DecorationFocus=61,174,233 +DecorationHover=61,174,233 +ForegroundActive=61,174,233 +ForegroundInactive=161,169,177 +ForegroundLink=29,153,243 +ForegroundNegative=218,68,83 +ForegroundNeutral=246,116,0 +ForegroundNormal=252,252,252 +ForegroundPositive=39,174,96 +ForegroundVisited=155,89,182 + +[General] +ColorSchemeHash=9d38eb71ded8ff549da26d57d7cbab9164da243a +UseSystemBell=true +XftAntialias=true +XftHintStyle=hintslight +XftSubPixel=none +fixed=Hack,14,-1,5,400,0,0,0,0,0,0,0,0,0,0,1,,0,0 +font=Noto Sans,14,-1,5,400,0,0,0,0,0,0,0,0,0,0,1,,0,0 +menuFont=Noto Sans,14,-1,5,400,0,0,0,0,0,0,0,0,0,0,1,,0,0 +smallestReadableFont=Noto Sans,12,-1,5,400,0,0,0,0,0,0,0,0,0,0,1,,0,0 +toolBarFont=Noto Sans,14,-1,5,400,0,0,0,0,0,0,0,0,0,0,1,,0,0 + +[KDE] +AnimationDurationFactor=0.25 +LookAndFeelPackage=org.kde.breezedark.desktop +contrast=4 +frameContrast=0.2 + +[KFileDialog Settings] +Allow Expansion=false +Automatically select filename extension=true +Breadcrumb Navigation=true +Decoration position=2 +Show Full Path=false +Show Inline Previews=true +Show Preview=false +Show Speedbar=true +Show hidden files=false +Sort by=Name +Sort directories first=true +Sort hidden files last=false +Sort reversed=false +Speedbar Width=140 +View Style=DetailTree + +[WM] +activeBackground=39,44,49 +activeBlend=252,252,252 +activeFont=Noto Sans,14,-1,5,400,0,0,0,0,0,0,0,0,0,0,1,,0,0 +activeForeground=252,252,252 +inactiveBackground=32,36,40 +inactiveBlend=161,169,177 +inactiveForeground=161,169,177 diff --git a/reference/home/.config/kglobalshortcutsrc b/reference/home/.config/kglobalshortcutsrc new file mode 100644 index 0000000..06ff1e1 --- /dev/null +++ b/reference/home/.config/kglobalshortcutsrc @@ -0,0 +1,277 @@ +[ActivityManager] +_k_friendly_name=Activity Manager +switch-to-activity-4dfe7a63-75b1-4532-89bb-9e8ebc7e360d=none,none,Switch to activity "Default" + +[KDE Keyboard Layout Switcher] +Switch to Last-Used Keyboard Layout=Meta+Alt+L,Meta+Alt+L,Switch to Last-Used Keyboard Layout +Switch to Next Keyboard Layout=Meta+Alt+K,Meta+Alt+K,Switch to Next Keyboard Layout +_k_friendly_name=Keyboard Layout Switcher + +[kaccess] +Toggle Screen Reader On and Off=Meta+Alt+S,Meta+Alt+S,Toggle Screen Reader On and Off +_k_friendly_name=Accessibility + +[kmix] +_k_friendly_name=Audio Volume +decrease_microphone_volume=Microphone Volume Down,Microphone Volume Down,Decrease Microphone Volume +decrease_volume=Volume Down,Volume Down,Decrease Volume +decrease_volume_small=Shift+Volume Down,Shift+Volume Down,Decrease Volume by 1% +increase_microphone_volume=Microphone Volume Up,Microphone Volume Up,Increase Microphone Volume +increase_volume=Volume Up,Volume Up,Increase Volume +increase_volume_small=Shift+Volume Up,Shift+Volume Up,Increase Volume by 1% +mic_mute=Microphone Mute\tMeta+Volume Mute,Microphone Mute\tMeta+Volume Mute,Mute Microphone +mute=Volume Mute,Volume Mute,Mute +push_to_talk=none,none,Push to talk + +[ksmserver] +Halt Without Confirmation=none,none,Shut Down Without Confirmation +Lock Session=Screensaver\tMeta+L,Screensaver\tMeta+L,Lock Session +Log Out=Ctrl+Alt+Del,Ctrl+Alt+Del,Show Logout Screen +Log Out Without Confirmation=none,none,Log Out Without Confirmation +LogOut=none,none,Log Out +Reboot=none,none,Reboot +Reboot Without Confirmation=none,none,Reboot Without Confirmation +Shut Down=none,none,Shut Down +_k_friendly_name=Session Management + +[kwin] +Activate Window Demanding Attention=Meta+Ctrl+A,Meta+Ctrl+A,Activate Window Demanding Attention +Cycle Overview=none,none,Cycle through Overview and Grid View +Cycle Overview Opposite=none,none,Cycle through Grid View and Overview +Decrease Opacity=none,none,Decrease Opacity of Active Window by 5% +Edit Tiles=Meta+T,Meta+T,Toggle Tiles Editor +Expose=Ctrl+F9\tMeta+F9,Ctrl+F9\tMeta+F9,Toggle Present Windows (Current desktop) +ExposeAll=Launch (C)\tCtrl+F10\tMeta+F10,Launch (C)\tCtrl+F10\tMeta+F10,Toggle Present Windows (All desktops) +ExposeClass=Ctrl+F7\tMeta+F7,Ctrl+F7\tMeta+F7,Toggle Present Windows (Window class) +ExposeClassCurrentDesktop=none,none,Toggle Present Windows (Window class on current desktop) +Grid View=Meta+G,Meta+G,Toggle Grid View +Increase Opacity=none,none,Increase Opacity of Active Window by 5% +Kill Window=Meta+Ctrl+Esc,Meta+Ctrl+Esc,Kill Window +Move Tablet to Next LogicalOutput=none,none,Move the tablet to the next output +MoveMouseToCenter=Meta+F6,Meta+F6,Move Mouse to Centre +MoveMouseToFocus=Meta+F5,Meta+F5,Move Mouse to Focus +MoveZoomDown=none,none,Move Zoomed Area Downwards +MoveZoomLeft=none,none,Move Zoomed Area to Left +MoveZoomRight=none,none,Move Zoomed Area to Right +MoveZoomUp=none,none,Move Zoomed Area Upwards +Overview=Meta+W,Meta+W,Toggle Overview +Setup Window Shortcut=none,none,Setup Window Shortcut +Show Desktop=Meta+D,Meta+D,Peek at Desktop +Switch One Desktop Down=Meta+Ctrl+Down,Meta+Ctrl+Down,Switch One Desktop Down +Switch One Desktop Up=Meta+Ctrl+Up,Meta+Ctrl+Up,Switch One Desktop Up +Switch One Desktop to the Left=Meta+Ctrl+Left,Meta+Ctrl+Left,Switch One Desktop to the Left +Switch One Desktop to the Right=Meta+Ctrl+Right,Meta+Ctrl+Right,Switch One Desktop to the Right +Switch Window Down=Meta+Alt+Down,Meta+Alt+Down,Switch to Window Below +Switch Window Left=Meta+Alt+Left,Meta+Alt+Left,Switch to Window to the Left +Switch Window Right=Meta+Alt+Right,Meta+Alt+Right,Switch to Window to the Right +Switch Window Up=Meta+Alt+Up,Meta+Alt+Up,Switch to Window Above +Switch to Desktop 1=Meta+1,Ctrl+F1\tMeta+F1,Switch to Desktop 1 +Switch to Desktop 10=none,none,Switch to Desktop 10 +Switch to Desktop 11=none,none,Switch to Desktop 11 +Switch to Desktop 12=none,none,Switch to Desktop 12 +Switch to Desktop 13=none,none,Switch to Desktop 13 +Switch to Desktop 14=none,none,Switch to Desktop 14 +Switch to Desktop 15=none,none,Switch to Desktop 15 +Switch to Desktop 16=none,none,Switch to Desktop 16 +Switch to Desktop 17=none,none,Switch to Desktop 17 +Switch to Desktop 18=none,none,Switch to Desktop 18 +Switch to Desktop 19=none,none,Switch to Desktop 19 +Switch to Desktop 2=Meta+2,Ctrl+F2\tMeta+F2,Switch to Desktop 2 +Switch to Desktop 20=none,none,Switch to Desktop 20 +Switch to Desktop 21=none,none,Switch to Desktop 21 +Switch to Desktop 22=none,none,Switch to Desktop 22 +Switch to Desktop 23=none,none,Switch to Desktop 23 +Switch to Desktop 24=none,none,Switch to Desktop 24 +Switch to Desktop 25=none,none,Switch to Desktop 25 +Switch to Desktop 3=Meta+3,Ctrl+F3\tMeta+F3,Switch to Desktop 3 +Switch to Desktop 4=Meta+4,Ctrl+F4\tMeta+F4,Switch to Desktop 4 +Switch to Desktop 5=Meta+5,none,Switch to Desktop 5 +Switch to Desktop 6=Meta+6,none,Switch to Desktop 6 +Switch to Desktop 7=Meta+7,none,Switch to Desktop 7 +Switch to Desktop 8=Meta+8,none,Switch to Desktop 8 +Switch to Desktop 9=Meta+9,none,Switch to Desktop 9 +Switch to Next Desktop=none,none,Switch to Next Desktop +Switch to Next Screen=none,none,Switch to Next Screen +Switch to Previous Desktop=none,none,Switch to Previous Desktop +Switch to Previous Screen=none,none,Switch to Previous Screen +Switch to Screen 0=none,none,Switch to Screen 0 +Switch to Screen 1=none,none,Switch to Screen 1 +Switch to Screen 2=none,none,Switch to Screen 2 +Switch to Screen 3=none,none,Switch to Screen 3 +Switch to Screen 4=none,none,Switch to Screen 4 +Switch to Screen 5=none,none,Switch to Screen 5 +Switch to Screen 6=none,none,Switch to Screen 6 +Switch to Screen 7=none,none,Switch to Screen 7 +Switch to Screen Above=none,none,Switch to Screen Above +Switch to Screen Below=none,none,Switch to Screen Below +Switch to Screen to the Left=none,none,Switch to Screen to the Left +Switch to Screen to the Right=none,none,Switch to Screen to the Right +Toggle Night Color=none,none,Suspend/Resume Night Light +Toggle Window Raise/Lower=none,none,Toggle Window Raise/Lower +Walk Through Windows=Alt+Tab\tMeta+Tab,Alt+Tab\tMeta+Tab,Walk Through Windows +Walk Through Windows (Reverse)=Alt+Shift+Tab\tMeta+Shift+Tab,Alt+Shift+Tab\tMeta+Shift+Tab,Walk Through Windows (Reverse) +Walk Through Windows Alternative=none,none,Walk Through Windows Alternative +Walk Through Windows Alternative (Reverse)=none,none,Walk Through Windows Alternative (Reverse) +Walk Through Windows of Current Application=Alt+`\tMeta+`,Alt+`\tMeta+`,Walk Through Windows of Current Application +Walk Through Windows of Current Application (Reverse)=Alt+~\tMeta+~,Alt+~\tMeta+~,Walk Through Windows of Current Application (Reverse) +Walk Through Windows of Current Application Alternative=none,none,Walk Through Windows of Current Application Alternative +Walk Through Windows of Current Application Alternative (Reverse)=none,none,Walk Through Windows of Current Application Alternative (Reverse) +Window Above Other Windows=none,none,Keep Window Above Others +Window Below Other Windows=none,none,Keep Window Below Others +Window Close=Meta+Shift+Q,Alt+F4,Close Window +Window Custom Quick Tile Bottom=none,none,Custom Quick Tile Window to the Bottom +Window Custom Quick Tile Left=none,none,Custom Quick Tile Window to the Left +Window Custom Quick Tile Right=none,none,Custom Quick Tile Window to the Right +Window Custom Quick Tile Top=none,none,Custom Quick Tile Window to the Top +Window Fullscreen=none,none,Make Window Fullscreen +Window Grow Horizontal=none,none,Expand Window Horizontally +Window Grow Vertical=none,none,Expand Window Vertically +Window Lower=none,none,Lower Window +Window Maximize=Meta+PgUp,Meta+PgUp,Maximise Window +Window Maximize Horizontal=none,none,Maximise Window Horizontally +Window Maximize Vertical=none,none,Maximise Window Vertically +Window Minimize=Meta+PgDown,Meta+PgDown,Minimise Window +Window Move=none,none,Move Window +Window Move Center=none,none,Move Window to the Centre +Window No Border=none,none,Toggle Window Titlebar and Frame +Window On All Desktops=none,none,Keep Window on All Desktops +Window One Desktop Down=Meta+Ctrl+Shift+Down,Meta+Ctrl+Shift+Down,Window One Desktop Down +Window One Desktop Up=Meta+Ctrl+Shift+Up,Meta+Ctrl+Shift+Up,Window One Desktop Up +Window One Desktop to the Left=Meta+Ctrl+Shift+Left,Meta+Ctrl+Shift+Left,Window One Desktop to the Left +Window One Desktop to the Right=Meta+Ctrl+Shift+Right,Meta+Ctrl+Shift+Right,Window One Desktop to the Right +Window One Screen Down=none,none,Move Window One Screen Down +Window One Screen Up=none,none,Move Window One Screen Up +Window One Screen to the Left=none,none,Move Window One Screen to the Left +Window One Screen to the Right=none,none,Move Window One Screen to the Right +Window Operations Menu=Alt+F3,Alt+F3,Window Menu +Window Pack Down=none,none,Move Window Down +Window Pack Left=none,none,Move Window Left +Window Pack Right=none,none,Move Window Right +Window Pack Up=none,none,Move Window Up +Window Quick Tile Bottom=Meta+Down,Meta+Down,Quick Tile Window to the Bottom +Window Quick Tile Bottom Left=none,none,Quick Tile Window to the Bottom Left +Window Quick Tile Bottom Right=none,none,Quick Tile Window to the Bottom Right +Window Quick Tile Left=Meta+Left,Meta+Left,Quick Tile Window to the Left +Window Quick Tile Right=Meta+Right,Meta+Right,Quick Tile Window to the Right +Window Quick Tile Top=Meta+Up,Meta+Up,Quick Tile Window to the Top +Window Quick Tile Top Left=none,none,Quick Tile Window to the Top Left +Window Quick Tile Top Right=none,none,Quick Tile Window to the Top Right +Window Raise=none,none,Raise Window +Window Resize=none,none,Resize Window +Window Restore=Meta+Backspace,Meta+Backspace,Restore Window +Window Shrink Horizontal=none,none,Shrink Window Horizontally +Window Shrink Vertical=none,none,Shrink Window Vertically +Window to Desktop 1=Meta+!,none,Window to Desktop 1 +Window to Desktop 10=none,none,Window to Desktop 10 +Window to Desktop 11=none,none,Window to Desktop 11 +Window to Desktop 12=none,none,Window to Desktop 12 +Window to Desktop 13=none,none,Window to Desktop 13 +Window to Desktop 14=none,none,Window to Desktop 14 +Window to Desktop 15=none,none,Window to Desktop 15 +Window to Desktop 16=none,none,Window to Desktop 16 +Window to Desktop 17=none,none,Window to Desktop 17 +Window to Desktop 18=none,none,Window to Desktop 18 +Window to Desktop 19=none,none,Window to Desktop 19 +Window to Desktop 2=Meta+@,none,Window to Desktop 2 +Window to Desktop 20=none,none,Window to Desktop 20 +Window to Desktop 21=none,none,Window to Desktop 21 +Window to Desktop 22=none,none,Window to Desktop 22 +Window to Desktop 23=none,none,Window to Desktop 23 +Window to Desktop 24=none,none,Window to Desktop 24 +Window to Desktop 25=none,none,Window to Desktop 25 +Window to Desktop 3=Meta+#,none,Window to Desktop 3 +Window to Desktop 4=Meta+$,none,Window to Desktop 4 +Window to Desktop 5=Meta+%,none,Window to Desktop 5 +Window to Desktop 6=Meta+^,none,Window to Desktop 6 +Window to Desktop 7=Meta+&,none,Window to Desktop 7 +Window to Desktop 8=Meta+*,none,Window to Desktop 8 +Window to Desktop 9=Meta+(,none,Window to Desktop 9 +Window to Next Desktop=none,none,Window to Next Desktop +Window to Next Screen=Meta+Shift+Right,Meta+Shift+Right,Move Window to Next Screen +Window to Previous Desktop=none,none,Window to Previous Desktop +Window to Previous Screen=Meta+Shift+Left,Meta+Shift+Left,Move Window to Previous Screen +Window to Screen 0=none,none,Move Window to Screen 0 +Window to Screen 1=none,none,Move Window to Screen 1 +Window to Screen 2=none,none,Move Window to Screen 2 +Window to Screen 3=none,none,Move Window to Screen 3 +Window to Screen 4=none,none,Move Window to Screen 4 +Window to Screen 5=none,none,Move Window to Screen 5 +Window to Screen 6=none,none,Move Window to Screen 6 +Window to Screen 7=none,none,Move Window to Screen 7 +_k_friendly_name=KWin +disableInputCapture=Meta+Shift+Esc,Meta+Shift+Esc,Disable Active Input Capture +view_actual_size=Meta+0,Meta+0,Zoom to Actual Size +view_zoom_in=Meta++\tMeta+=,Meta++\tMeta+=,Zoom In +view_zoom_out=Meta+-,Meta+-,Zoom Out + +[mediacontrol] +_k_friendly_name=Media Controller +mediavolumedown=none,none,Media volume down +mediavolumeup=none,none,Media volume up +nextmedia=Media Next,Media Next,Media playback next +pausemedia=Media Pause,Media Pause,Pause media playback +playmedia=none,none,Play media playback +playpausemedia=Media Play,Media Play,Play/Pause media playback +previousmedia=Media Previous,Media Previous,Media playback previous +seekbackwardmedia=Media Rewind,Media Rewind,Media playback seek backward 5s +seekbackwardmedialong=none,none,Media playback seek backward 30s +seekforwardmedia=Media Fast Forward,Media Fast Forward,Media playback seek forward 5s +seekforwardmedialong=none,none,Media playback seek forward 30s +stopmedia=Media Stop,Media Stop,Stop media playback + +[org_kde_powerdevil] +Decrease Keyboard Brightness=Keyboard Brightness Down,Keyboard Brightness Down,Decrease Keyboard Brightness +Decrease Screen Brightness=Monitor Brightness Down,Monitor Brightness Down,Decrease Screen Brightness +Decrease Screen Brightness Small=Shift+Monitor Brightness Down,Shift+Monitor Brightness Down,Decrease Screen Brightness by 1% +Hibernate=Hibernate,Hibernate,Hibernate +Increase Keyboard Brightness=Keyboard Brightness Up,Keyboard Brightness Up,Increase Keyboard Brightness +Increase Screen Brightness=Monitor Brightness Up,Monitor Brightness Up,Increase Screen Brightness +Increase Screen Brightness Small=Shift+Monitor Brightness Up,Shift+Monitor Brightness Up,Increase Screen Brightness by 1% +PowerDown=Power Down,Power Down,Power Down +PowerOff=Power Off,Power Off,Power Off +Sleep=Sleep,Sleep,Suspend +Toggle Keyboard Backlight=Keyboard Light On/Off,Keyboard Light On/Off,Toggle Keyboard Backlight +Turn Off Screen=none,none,Turn Off Screen +_k_friendly_name=Power Management +powerProfile=Battery\tMeta+B,Battery\tMeta+B,Switch Power Profile + +[plasmashell] +Slideshow Wallpaper Next Image=none,none,Next Wallpaper Image +_k_friendly_name=plasmashell +activate application launcher=Meta\tAlt+F1,Meta\tAlt+F1,Activate Application Launcher +activate task manager entry 1=none,Meta+1,Activate Task Manager Entry 1 +activate task manager entry 10=none,none,Activate Task Manager Entry 10 +activate task manager entry 2=none,Meta+2,Activate Task Manager Entry 2 +activate task manager entry 3=none,Meta+3,Activate Task Manager Entry 3 +activate task manager entry 4=none,Meta+4,Activate Task Manager Entry 4 +activate task manager entry 5=none,Meta+5,Activate Task Manager Entry 5 +activate task manager entry 6=none,Meta+6,Activate Task Manager Entry 6 +activate task manager entry 7=none,Meta+7,Activate Task Manager Entry 7 +activate task manager entry 8=none,Meta+8,Activate Task Manager Entry 8 +activate task manager entry 9=none,Meta+9,Activate Task Manager Entry 9 +clear history=none,none,Clear Notification History +clear-history=none,none,Clear Clipboard History +clipboard_action=Meta+Ctrl+X,Meta+Ctrl+X,Automatic Action Popup Menu +cycle-panels=Meta+Alt+P,Meta+Alt+P,Move keyboard focus between panels +cycleNextAction=none,none,Next History Item +cyclePrevAction=none,none,Previous History Item +edit_clipboard=none,none,Edit Contents… +manage activities=Meta+Q,Meta+Q,Show Activity Switcher +next activity=Meta+A,none,Walk through activities +previous activity=Meta+Shift+A,none,Walk through activities (Reverse) +repeat_action=none,none,Manually Invoke Action on Current Clipboard +show dashboard=Ctrl+F12,Ctrl+F12,Show Desktop +show-barcode=none,none,Show Barcode… +show-on-mouse-pos=Meta+V,Meta+V,Show Clipboard Items at Mouse Position +switch to next activity=none,none,Switch to Next Activity +switch to previous activity=none,none,Switch to Previous Activity +toggle do not disturb=none,none,Toggle do not disturb + +[services][Alacritty.desktop] +_launch=Meta+Return + +[services][net.local.fish.desktop] +_launch=Meta+Ctrl+Space + +[services][org.kde.konsole.desktop] +_launch=none diff --git a/reference/home/.config/konsolerc b/reference/home/.config/konsolerc new file mode 100644 index 0000000..ad9bfb8 --- /dev/null +++ b/reference/home/.config/konsolerc @@ -0,0 +1,5 @@ +[General] +ConfigVersion=1 + +[UiSettings] +ColorScheme= diff --git a/reference/home/.config/kwinoutputconfig.json b/reference/home/.config/kwinoutputconfig.json new file mode 100644 index 0000000..98b4a89 --- /dev/null +++ b/reference/home/.config/kwinoutputconfig.json @@ -0,0 +1,93 @@ +[ + { + "data": [ + { + "abmLevel": 0, + "allowDdcCi": true, + "allowSdrSoftwareBrightness": false, + "autoBrightnessCurve": [ + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0 + ], + "autoRotation": "InTabletMode", + "automaticBrightness": false, + "brightness": 1, + "colorPowerTradeoff": "PreferEfficiency", + "colorProfileSource": "sRGB", + "connectorName": "eDP-1", + "customModes": [ + ], + "detectedDdcCi": false, + "edidHash": "5f3fe94f152da0c618ae754a160d08df", + "edidIdentifier": "AUO 23341 0 0 2018 0", + "edrPolicy": "always", + "hdrColorProfileSource": "EDID", + "hdrIccProfilePath": "", + "highDynamicRange": false, + "iccProfilePath": "", + "maxBitsPerColor": 0, + "mode": { + "flags": 1, + "height": 1080, + "refreshRate": 60033, + "width": 1920 + }, + "overscan": 0, + "rgbRange": "Automatic", + "scale": 1, + "sdrBrightness": 200, + "sdrGamutWideness": 0, + "sharpness": 0, + "transform": "Normal", + "uuid": "3f5a91e5-1c16-44d8-a5f5-b5854daa57be", + "vrrPolicy": "Never", + "wideColorGamut": false + } + ], + "name": "outputs" + }, + { + "data": [ + { + "lidClosed": false, + "outputs": [ + { + "enabled": true, + "outputIndex": 0, + "position": { + "x": 0, + "y": 0 + }, + "priority": 1, + "replicationSource": "" + } + ] + }, + { + "lidClosed": true, + "outputs": [ + { + "enabled": true, + "outputIndex": 0, + "position": { + "x": 0, + "y": 0 + }, + "priority": 0, + "replicationSource": "" + } + ] + } + ], + "name": "setups" + } +] diff --git a/reference/home/.config/kwinrc b/reference/home/.config/kwinrc new file mode 100644 index 0000000..6d0079e --- /dev/null +++ b/reference/home/.config/kwinrc @@ -0,0 +1,60 @@ +[Desktops] +Id_1=4c183a08-13bd-40a0-8131-12584ba0b0c0 +Id_2=b20e6cf5-12e6-44c9-b8f1-0bb5a5e2307a +Id_3=77ff2787-e9df-4539-b009-8da2084b3ba0 +Id_4=f4bc9302-9b20-4cbb-b73b-4cfecba52889 +Id_5=e0032b5e-9f48-4219-8800-152a21b01604 +Id_6=a3ac962a-66e0-4cfe-a9ca-a789eae23a3a +Id_7=6ecab170-20d2-4383-b152-0d443d77b8ad +Id_8=ba6489cf-4f95-4786-af9f-3d742e17e6c5 +Id_9=5f9b8e88-3740-4d2c-9ac5-e76e072606f4 +Number=9 +Rows=1 + +[Effect-zoom] +InitialZoom=4.136880223300198 + +[Plugins] +zoomEnabled=false + +[Tiling][4c183a08-13bd-40a0-8131-12584ba0b0c0][3f5a91e5-1c16-44d8-a5f5-b5854daa57be] +padding=4 +tiles={"layoutDirection":"horizontal","tiles":[{"width":0.25},{"width":0.5},{"width":0.25}]} + +[Tiling][5f9b8e88-3740-4d2c-9ac5-e76e072606f4][3f5a91e5-1c16-44d8-a5f5-b5854daa57be] +padding=4 +tiles={"layoutDirection":"horizontal","tiles":[{"width":0.25},{"width":0.5},{"width":0.25}]} + +[Tiling][6ecab170-20d2-4383-b152-0d443d77b8ad][3f5a91e5-1c16-44d8-a5f5-b5854daa57be] +padding=4 +tiles={"layoutDirection":"horizontal","tiles":[{"width":0.25},{"width":0.5},{"width":0.25}]} + +[Tiling][77ff2787-e9df-4539-b009-8da2084b3ba0][3f5a91e5-1c16-44d8-a5f5-b5854daa57be] +padding=4 +tiles={"layoutDirection":"horizontal","tiles":[{"width":0.25},{"width":0.5},{"width":0.25}]} + +[Tiling][a3ac962a-66e0-4cfe-a9ca-a789eae23a3a][3f5a91e5-1c16-44d8-a5f5-b5854daa57be] +padding=4 +tiles={"layoutDirection":"horizontal","tiles":[{"width":0.25},{"width":0.5},{"width":0.25}]} + +[Tiling][b20e6cf5-12e6-44c9-b8f1-0bb5a5e2307a][3f5a91e5-1c16-44d8-a5f5-b5854daa57be] +padding=4 +tiles={"layoutDirection":"horizontal","tiles":[{"width":0.25},{"width":0.5},{"width":0.25}]} + +[Tiling][ba6489cf-4f95-4786-af9f-3d742e17e6c5][3f5a91e5-1c16-44d8-a5f5-b5854daa57be] +padding=4 +tiles={"layoutDirection":"horizontal","tiles":[{"width":0.25},{"width":0.5},{"width":0.25}]} + +[Tiling][e0032b5e-9f48-4219-8800-152a21b01604][3f5a91e5-1c16-44d8-a5f5-b5854daa57be] +padding=4 +tiles={"layoutDirection":"horizontal","tiles":[{"width":0.25},{"width":0.5},{"width":0.25}]} + +[Tiling][f4bc9302-9b20-4cbb-b73b-4cfecba52889][3f5a91e5-1c16-44d8-a5f5-b5854daa57be] +padding=4 +tiles={"layoutDirection":"horizontal","tiles":[{"width":0.25},{"width":0.5},{"width":0.25}]} + +[Windows] +RollOverDesktops=true + +[Xwayland] +Scale=1 diff --git a/reference/home/.config/kxkbrc b/reference/home/.config/kxkbrc new file mode 100644 index 0000000..4ba34e1 --- /dev/null +++ b/reference/home/.config/kxkbrc @@ -0,0 +1,3 @@ +[Layout] +Options=caps:escape_shifted_capslock +ResetOldOptions=true diff --git a/reference/home/.config/micro/colorschemes/catppuccin-frappe.micro b/reference/home/.config/micro/colorschemes/catppuccin-frappe.micro new file mode 100644 index 0000000..6bdff93 --- /dev/null +++ b/reference/home/.config/micro/colorschemes/catppuccin-frappe.micro @@ -0,0 +1,42 @@ +color-link default "#C6D0F5,#303446" +color-link comment "#626880" + +color-link identifier "#8CAAEE" +color-link identifier.class "#8CAAEE" +color-link identifier.var "#8CAAEE" + +color-link constant "#EF9F76" +color-link constant.number "#EF9F76" +color-link constant.string "#A6D189" + +color-link symbol "#F4B8E4" +color-link symbol.brackets "#EEBEBE" +color-link symbol.tag "#8CAAEE" + +color-link type "#8CAAEE" +color-link type.keyword "#E5C890" + +color-link special "#F4B8E4" +color-link statement "#CA9EE6" +color-link preproc "#F4B8E4" + +color-link underlined "#99D1DB" +color-link error "bold #E78284" +color-link todo "bold #E5C890" + +color-link diff-added "#A6D189" +color-link diff-modified "#E5C890" +color-link diff-deleted "#E78284" + +color-link gutter-error "#E78284" +color-link gutter-warning "#E5C890" + +color-link statusline "#F2D5CF,#292C3C" +color-link tabbar "#F2D5CF,#292C3C" +color-link indent-char "#51576D" +color-link line-number "#51576D" +color-link current-line-number "#BABBF1" + +color-link cursor-line "#414559,#C6D0F5" +color-link color-column "#414559" +color-link type.extended "default" diff --git a/reference/home/.config/micro/colorschemes/catppuccin-latte.micro b/reference/home/.config/micro/colorschemes/catppuccin-latte.micro new file mode 100644 index 0000000..80b3727 --- /dev/null +++ b/reference/home/.config/micro/colorschemes/catppuccin-latte.micro @@ -0,0 +1,42 @@ +color-link default "#4C4F69,#EFF1F5" +color-link comment "#ACB0BE" + +color-link identifier "#1E66F5" +color-link identifier.class "#1E66F5" +color-link identifier.var "#1E66F5" + +color-link constant "#FE640B" +color-link constant.number "#FE640B" +color-link constant.string "#40A02B" + +color-link symbol "#EA76CB" +color-link symbol.brackets "#DD7878" +color-link symbol.tag "#1E66F5" + +color-link type "#1E66F5" +color-link type.keyword "#DF8E1D" + +color-link special "#EA76CB" +color-link statement "#8839EF" +color-link preproc "#EA76CB" + +color-link underlined "#04A5E5" +color-link error "bold #D20F39" +color-link todo "bold #DF8E1D" + +color-link diff-added "#40A02B" +color-link diff-modified "#DF8E1D" +color-link diff-deleted "#D20F39" + +color-link gutter-error "#D20F39" +color-link gutter-warning "#DF8E1D" + +color-link statusline "#4C4F69,#DCE0E8" +color-link tabbar "#4C4F69,#DCE0E8" +color-link indent-char "#BCC0CC" +color-link line-number "#BCC0CC" +color-link current-line-number "#7287FD" + +color-link cursor-line "#CCD0DA,#4C4F69" +color-link color-column "#CCD0DA" +color-link type.extended "default" diff --git a/reference/home/.config/micro/colorschemes/catppuccin-macchiato.micro b/reference/home/.config/micro/colorschemes/catppuccin-macchiato.micro new file mode 100644 index 0000000..8634683 --- /dev/null +++ b/reference/home/.config/micro/colorschemes/catppuccin-macchiato.micro @@ -0,0 +1,42 @@ +color-link default "#CAD3F5,#24273A" +color-link comment "#5B6078" + +color-link identifier "#8AADF4" +color-link identifier.class "#8AADF4" +color-link identifier.var "#8AADF4" + +color-link constant "#F5A97F" +color-link constant.number "#F5A97F" +color-link constant.string "#A6DA95" + +color-link symbol "#F5BDE6" +color-link symbol.brackets "#F0C6C6" +color-link symbol.tag "#8AADF4" + +color-link type "#8AADF4" +color-link type.keyword "#EED49F" + +color-link special "#F5BDE6" +color-link statement "#C6A0F6" +color-link preproc "#F5BDE6" + +color-link underlined "#8AADF4" +color-link error "bold #ED8796" +color-link todo "bold #EED49F" + +color-link diff-added "#A6DA95" +color-link diff-modified "#EED49F" +color-link diff-deleted "#ED8796" + +color-link gutter-error "#ED8796" +color-link gutter-warning "#EED49F" + +color-link statusline "#F4DBD6,#1E2030" +color-link tabbar "#F4DBD6,#1E2030" +color-link indent-char "#494D64" +color-link line-number "#494D64" +color-link current-line-number "#B7BDF8" + +color-link cursor-line "#363A4F,#CAD3F5" +color-link color-column "#363A4F" +color-link type.extended "default" diff --git a/reference/home/.config/micro/colorschemes/catppuccin-mocha.micro b/reference/home/.config/micro/colorschemes/catppuccin-mocha.micro new file mode 100644 index 0000000..d00718c --- /dev/null +++ b/reference/home/.config/micro/colorschemes/catppuccin-mocha.micro @@ -0,0 +1,42 @@ +color-link default "#C6D0F5,#1E1E2E" +color-link comment "#585B70" + +color-link identifier "#89B4FA" +color-link identifier.class "#89B4FA" +color-link identifier.var "#89B4FA" + +color-link constant "#FAB387" +color-link constant.number "#FAB387" +color-link constant.string "#A6E3A1" + +color-link symbol "#F5C2E7" +color-link symbol.brackets "#F2CDCD" +color-link symbol.tag "#89B4FA" + +color-link type "#89B4FA" +color-link type.keyword "#F9E2AF" + +color-link special "#F5C2E7" +color-link statement "#CBA6F7" +color-link preproc "#F5C2E7" + +color-link underlined "#89DCEB" +color-link error "bold #F38BA8" +color-link todo "bold #F9E2AF" + +color-link diff-added "#A6E3A1" +color-link diff-modified "#F9E2AF" +color-link diff-deleted "#F38BA8" + +color-link gutter-error "#F38BA8" +color-link gutter-warning "#F9E2AF" + +color-link statusline "#F5E0DC,#181825" +color-link tabbar "#F5E0DC,#181825" +color-link indent-char "#45475A" +color-link line-number "#45475A" +color-link current-line-number "#B4BEFE" + +color-link cursor-line "#313244,#C6D0F5" +color-link color-column "#313244" +color-link type.extended "default" diff --git a/reference/home/.config/micro/settings.json b/reference/home/.config/micro/settings.json new file mode 100644 index 0000000..eef4c52 --- /dev/null +++ b/reference/home/.config/micro/settings.json @@ -0,0 +1,3 @@ +{ + "colorscheme": "catppuccin-macchiato" +} diff --git a/reference/home/.config/mimeapps.list b/reference/home/.config/mimeapps.list new file mode 100644 index 0000000..03da0ea --- /dev/null +++ b/reference/home/.config/mimeapps.list @@ -0,0 +1,4 @@ + +[Default Applications] +x-scheme-handler/claude-cli=claude-code-url-handler.desktop +x-scheme-handler/openwhispr=openwhispr.desktop diff --git a/reference/home/.config/nvim/after/ftplugin/markdown.lua b/reference/home/.config/nvim/after/ftplugin/markdown.lua new file mode 100644 index 0000000..2848685 --- /dev/null +++ b/reference/home/.config/nvim/after/ftplugin/markdown.lua @@ -0,0 +1 @@ +vim.opt_local.conceallevel = 2 diff --git a/reference/home/.config/nvim/init.lua b/reference/home/.config/nvim/init.lua new file mode 100644 index 0000000..5c7f4ef --- /dev/null +++ b/reference/home/.config/nvim/init.lua @@ -0,0 +1,3 @@ +require("vim_options") +require("keys") +require("plugin") diff --git a/reference/home/.config/nvim/lazy-lock.json b/reference/home/.config/nvim/lazy-lock.json new file mode 100644 index 0000000..2ec59b6 --- /dev/null +++ b/reference/home/.config/nvim/lazy-lock.json @@ -0,0 +1,13 @@ +{ + "diffview.nvim": { "branch": "main", "commit": "4516612fe98ff56ae0415a259ff6361a89419b0a" }, + "gitsigns.nvim": { "branch": "main", "commit": "eb60cc7b94c46005237fd34170d76f3a089a90aa" }, + "lazy.nvim": { "branch": "main", "commit": "306a05526ada86a7b30af95c5cc81ffba93fef97" }, + "neogit": { "branch": "master", "commit": "6fc2fa890bd2031ed999c074daab0fb4feff20a5" }, + "nord.nvim": { "branch": "main", "commit": "87394d4fc35c901bbe38326a78d31ab1ead826b6" }, + "nvim-treesitter": { "branch": "master", "commit": "cf12346a3414fa1b06af75c79faebe7f76df080a" }, + "oil.nvim": { "branch": "master", "commit": "b73018b75affd13fa38e2fc94ef753b465f770d7" }, + "plenary.nvim": { "branch": "master", "commit": "74b06c6c75e4eeb3108ec01852001636d85a932b" }, + "render-markdown.nvim": { "branch": "main", "commit": "f422cb5c6855f150e2ddcfaf44e7157b98b34f6a" }, + "snacks.nvim": { "branch": "main", "commit": "882c996cf28183f4d63640de0b4c02ec886d01f2" }, + "which-key.nvim": { "branch": "main", "commit": "3aab2147e74890957785941f0c1ad87d0a44c15a" } +} diff --git a/reference/home/.config/nvim/lua/keys.lua b/reference/home/.config/nvim/lua/keys.lua new file mode 100644 index 0000000..486b5da --- /dev/null +++ b/reference/home/.config/nvim/lua/keys.lua @@ -0,0 +1,8 @@ +local map = vim.keymap.set + +map("n", "", "h", { desc = "Move focus left" }) +map("n", "", "j", { desc = "Move focus down" }) +map("n", "", "k", { desc = "Move focus up" }) +map("n", "", "l", { desc = "Move focus right" }) + +map("n", "", "nohlsearch", { desc = "Clear search highlight" }) diff --git a/reference/home/.config/nvim/lua/plugin.lua b/reference/home/.config/nvim/lua/plugin.lua new file mode 100644 index 0000000..d2eea07 --- /dev/null +++ b/reference/home/.config/nvim/lua/plugin.lua @@ -0,0 +1,23 @@ +local lazypath = vim.fn.stdpath("data") .. "/lazy/lazy.nvim" +if not vim.uv.fs_stat(lazypath) then + local lazyrepo = "https://github.com/folke/lazy.nvim.git" + local out = vim.fn.system({ "git", "clone", "--filter=blob:none", "--branch=stable", lazyrepo, lazypath }) + if vim.v.shell_error ~= 0 then + vim.api.nvim_echo({ + { "Failed to clone lazy.nvim:\n", "ErrorMsg" }, + { out, "WarningMsg" }, + { "\nPress any key to exit..." }, + }, true, {}) + vim.fn.getchar() + os.exit(1) + end +end +vim.opt.rtp:prepend(lazypath) + +require("lazy").setup({ + spec = { + { import = "plugins" }, + }, + install = { colorscheme = { "nord" } }, + checker = { enabled = false }, +}) diff --git a/reference/home/.config/nvim/lua/plugins/git.lua b/reference/home/.config/nvim/lua/plugins/git.lua new file mode 100644 index 0000000..94d7889 --- /dev/null +++ b/reference/home/.config/nvim/lua/plugins/git.lua @@ -0,0 +1,36 @@ +return { + { + "NeogitOrg/neogit", + dependencies = { + "nvim-lua/plenary.nvim", + "sindrets/diffview.nvim", + }, + keys = { + { + "g", + function() + require("gitsigns").toggle_current_line_blame(true) + require("neogit").open() + end, + desc = "Open git (Neogit)", + }, + }, + config = function() + require("neogit").setup() + vim.api.nvim_create_autocmd("BufUnload", { + callback = function(args) + if vim.bo[args.buf].filetype == "NeogitStatus" then + require("gitsigns").toggle_current_line_blame(false) + end + end, + }) + end, + }, + { + "lewis6991/gitsigns.nvim", + event = "BufWinEnter", + opts = { + current_line_blame = false, + }, + }, +} diff --git a/reference/home/.config/nvim/lua/plugins/navigation.lua b/reference/home/.config/nvim/lua/plugins/navigation.lua new file mode 100644 index 0000000..efa86f9 --- /dev/null +++ b/reference/home/.config/nvim/lua/plugins/navigation.lua @@ -0,0 +1,27 @@ +return { + { + "stevearc/oil.nvim", + lazy = false, + opts = { + view_options = { show_hidden = true }, + }, + keys = { + { "e", "Oil", desc = "Open file browser" }, + }, + }, + { + "folke/snacks.nvim", + priority = 1000, + lazy = false, + opts = { + picker = { enabled = true }, + notifier = { enabled = true }, + input = { enabled = true }, + }, + keys = { + { "f", function() require("snacks").picker.files() end, desc = "Find files" }, + { "s", function() require("snacks").picker.grep() end, desc = "Search text" }, + { "b", function() require("snacks").picker.buffers() end, desc = "Switch buffer" }, + }, + }, +} diff --git a/reference/home/.config/nvim/lua/plugins/ui.lua b/reference/home/.config/nvim/lua/plugins/ui.lua new file mode 100644 index 0000000..5127f0a --- /dev/null +++ b/reference/home/.config/nvim/lua/plugins/ui.lua @@ -0,0 +1,55 @@ +return { + { + "gbprod/nord.nvim", + lazy = false, + priority = 1000, + opts = { + transparent = true, + }, + config = function(_, opts) + require("nord").setup(opts) + vim.cmd.colorscheme("nord") + end, + }, + { + "MeanderingProgrammer/render-markdown.nvim", + ft = { "markdown" }, + dependencies = { "nvim-treesitter/nvim-treesitter" }, + opts = {}, + }, + { + "folke/which-key.nvim", + lazy = false, + config = true, + }, + { + "nvim-treesitter/nvim-treesitter", + branch = "master", + build = ":TSUpdate", + opts = { + ensure_installed = { + "markdown", + "markdown_inline", + "lua", + "bash", + "fish", + "rust", + "javascript", + "typescript", + "java", + "kotlin", + "c", + "cpp", + "html", + "css", + "python", + }, + auto_install = false, + highlight = { enable = true }, + indent = { enable = true }, + }, + config = function(_, opts) + require("nvim-treesitter.configs").setup(opts) + end, + }, +} diff --git a/reference/home/.config/nvim/lua/vim_options.lua b/reference/home/.config/nvim/lua/vim_options.lua new file mode 100644 index 0000000..f8ee951 --- /dev/null +++ b/reference/home/.config/nvim/lua/vim_options.lua @@ -0,0 +1,29 @@ +vim.g.mapleader = " " + +local opt = vim.opt + +-- Clipboard: use neovim's built-in OSC 52 provider, no external binary needed. +vim.g.clipboard = "osc52" +opt.clipboard = "unnamedplus" + +opt.number = true +opt.relativenumber = true + +opt.shiftwidth = 2 +opt.tabstop = 2 +opt.expandtab = true + +opt.mouse = "a" + +opt.undofile = true + +opt.ignorecase = true +opt.smartcase = true + +opt.splitright = true +opt.splitbelow = true + +opt.wrap = false + +opt.scrolloff = 8 +opt.cursorline = true diff --git a/reference/home/.config/plasma-org.kde.plasma.desktop-appletsrc b/reference/home/.config/plasma-org.kde.plasma.desktop-appletsrc new file mode 100644 index 0000000..700fe24 --- /dev/null +++ b/reference/home/.config/plasma-org.kde.plasma.desktop-appletsrc @@ -0,0 +1,173 @@ +[ActionPlugins][0] +MiddleButton;NoModifier=org.kde.paste +RightButton;NoModifier=org.kde.contextmenu + +[ActionPlugins][1] +RightButton;NoModifier=org.kde.contextmenu + +[Containments][23] +activityId= +formfactor=2 +immutability=1 +lastScreen=0 +location=4 +plugin=org.kde.panel +wallpaperplugin=org.kde.image + +[Containments][23][Applets][24] +immutability=1 +plugin=org.kde.plasma.kickoff + +[Containments][23][Applets][24][Configuration] +popupHeight=593 +popupWidth=789 + +[Containments][23][Applets][24][Configuration][ConfigDialog] +DialogHeight=540 +DialogWidth=720 + +[Containments][23][Applets][24][Configuration][General] +favoritesPortedToKAstats=true +icon=org.cachyos.hello +systemFavorites=suspend\\,hibernate\\,reboot\\,shutdown + +[Containments][23][Applets][24][Configuration][Shortcuts] +global=Alt+F1 + +[Containments][23][Applets][25] +immutability=1 +plugin=org.kde.plasma.pager + +[Containments][23][Applets][26] +immutability=1 +plugin=org.kde.plasma.icontasks + +[Containments][23][Applets][26][Configuration][ConfigDialog] +DialogHeight=630 +DialogWidth=810 + +[Containments][23][Applets][26][Configuration][General] +launchers= + +[Containments][23][Applets][27] +immutability=1 +plugin=org.kde.plasma.marginsseparator + +[Containments][23][Applets][28] +activityId= +formfactor=2 +immutability=1 +lastScreen=0 +location=4 +plugin=org.kde.plasma.systemtray +popupHeight=432 +popupWidth=432 +wallpaperplugin=org.kde.image + +[Containments][23][Applets][28][Applets][29] +immutability=1 +plugin=org.kde.kdeconnect + +[Containments][23][Applets][28][Applets][30] +immutability=1 +plugin=org.kde.kscreen + +[Containments][23][Applets][28][Applets][31] +immutability=1 +plugin=org.kde.plasma.cameraindicator + +[Containments][23][Applets][28][Applets][32] +immutability=1 +plugin=org.kde.plasma.clipboard + +[Containments][23][Applets][28][Applets][33] +immutability=1 +plugin=org.kde.plasma.devicenotifier + +[Containments][23][Applets][28][Applets][34] +immutability=1 +plugin=org.kde.plasma.keyboardlayout + +[Containments][23][Applets][28][Applets][35] +immutability=1 +plugin=org.kde.plasma.manage-inputmethod + +[Containments][23][Applets][28][Applets][36] +immutability=1 +plugin=org.kde.plasma.notifications + +[Containments][23][Applets][28][Applets][37] +immutability=1 +plugin=org.kde.plasma.keyboardindicator + +[Containments][23][Applets][28][Applets][37][Configuration][General] +migrated=true + +[Containments][23][Applets][28][Applets][38] +immutability=1 +plugin=org.kde.plasma.weather + +[Containments][23][Applets][28][Applets][40] +immutability=1 +plugin=org.kde.plasma.battery + +[Containments][23][Applets][28][Applets][41] +immutability=1 +plugin=org.kde.plasma.brightness + +[Containments][23][Applets][28][Applets][42] +immutability=1 +plugin=org.kde.plasma.networkmanagement + +[Containments][23][Applets][28][Applets][43] +immutability=1 +plugin=org.kde.plasma.volume + +[Containments][23][Applets][28][Applets][43][Configuration][General] +migrated=true + +[Containments][23][Applets][28][Applets][44] +immutability=1 +plugin=org.kde.plasma.bluetooth + +[Containments][23][Applets][28][Applets][47] +immutability=1 +plugin=org.kde.plasma.mediacontroller + +[Containments][23][Applets][28][General] +extraItems=org.kde.kscreen,org.kde.plasma.battery,org.kde.plasma.brightness,org.kde.plasma.cameraindicator,org.kde.plasma.clipboard,org.kde.plasma.devicenotifier,org.kde.plasma.keyboardlayout,org.kde.plasma.manage-inputmethod,org.kde.plasma.mediacontroller,org.kde.plasma.networkmanagement,org.kde.plasma.notifications,org.kde.plasma.volume,org.kde.kdeconnect,org.kde.plasma.bluetooth,org.kde.plasma.keyboardindicator,org.kde.plasma.weather +knownItems=org.kde.kscreen,org.kde.plasma.battery,org.kde.plasma.brightness,org.kde.plasma.cameraindicator,org.kde.plasma.clipboard,org.kde.plasma.devicenotifier,org.kde.plasma.keyboardlayout,org.kde.plasma.manage-inputmethod,org.kde.plasma.mediacontroller,org.kde.plasma.networkmanagement,org.kde.plasma.notifications,org.kde.plasma.volume,org.kde.kdeconnect,org.kde.plasma.bluetooth,org.kde.plasma.keyboardindicator,org.kde.plasma.weather + +[Containments][23][Applets][38] +immutability=1 +plugin=org.kde.plasma.digitalclock + +[Containments][23][Applets][38][Configuration] +popupHeight=400 +popupWidth=560 + +[Containments][23][Applets][39] +immutability=1 +plugin=org.kde.plasma.showdesktop + +[Containments][23][General] +AppletOrder=24;25;26;27;28;38;39 + +[Containments][4][Wallpaper][org.kde.image][General] +Image=/usr/share/wallpapers/cachyos-wallpapers/north.png +PreviewImage=/usr/share/wallpapers/cachyos-wallpapers/north.png +SlidePaths=/usr/share/wallpapers/ + +[Containments][43] +ItemGeometries-1920x1080= +ItemGeometriesHorizontal= +activityId=4dfe7a63-75b1-4532-89bb-9e8ebc7e360d +formfactor=0 +immutability=1 +lastScreen=0 +location=0 +plugin=org.kde.plasma.folder +wallpaperplugin=org.kde.image + +[ScreenMapping] +itemsOnDisabledScreens= diff --git a/reference/home/.config/powermanagementprofilesrc b/reference/home/.config/powermanagementprofilesrc new file mode 100644 index 0000000..0f36073 --- /dev/null +++ b/reference/home/.config/powermanagementprofilesrc @@ -0,0 +1,2 @@ +[Migration] +MigratedProfilesToPlasma6=powerdevilrc diff --git a/reference/home/.config/shelly/config.json b/reference/home/.config/shelly/config.json new file mode 100644 index 0000000..b45ea16 --- /dev/null +++ b/reference/home/.config/shelly/config.json @@ -0,0 +1,56 @@ +{ + "FileSizeDisplay": "Megabytes", + "DefaultExecution": "UpgradeAll", + "ParallelDownloadCount": 10, + "Culture": "", + "AurEnabled": true, + "ShellySearchEnabled": false, + "AurWarningConfirmed": true, + "FlatPackEnabled": true, + "WindowWidth": 1021, + "WindowHeight": 600, + "DefaultView": "HomeScreen", + "UseOldMenu": false, + "TrayEnabled": true, + "TrayCheckIntervalHours": 72, + "NoConfirm": false, + "NewInstall": false, + "CurrentVersion": "2.4.1.2", + "UseWeeklySchedule": false, + "DaysOfWeek": [], + "Time": null, + "ShellyIconsEnabled": true, + "AppImageEnabled": true, + "NewInstallInitSettings": true, + "UseSymbolicTray": true, + "RemoveCache": false, + "TrayIconPath": "", + "TrayUpdatesIconPath": "", + "DefaultPageDropDown": "Packages", + "RecommendedEnabled": true, + "ProgressBarStyle": "Blocks", + "ProgressBarFps": 7, + "ProgressBarWidth": 24, + "OutputMode": "singlepane", + "SinglePaneMaxStickies": 6, + "TrayAutoStart": false, + "PackageDowngradeEnabled": false, + "PackageManagementCascadeDelete": true, + "PackageManagementRemoveConfigs": false, + "PackageManagementRemoveOptionalDeps": true, + "PackageManagementShowHidden": false, + "PackageInstallUpgrade": false, + "PackageInstallShowHidden": false, + "PackageUpdateShowHidden": false, + "AurInstallUseChroot": false, + "AurInstallRunChecks": false, + "AurRemoveCascadeDelete": true, + "AurRemoveShowHidden": false, + "AurUpdateRunChecks": false, + "AurUpdateShowHidden": false, + "AppImageInstallPath": "", + "StarFishEnabled": false, + "PackageInstallView": "Grid", + "PackageUpdateView": "Grid", + "PackageManageView": "Grid" +} \ No newline at end of file diff --git a/reference/home/.config/spectaclerc b/reference/home/.config/spectaclerc new file mode 100644 index 0000000..364a9e3 --- /dev/null +++ b/reference/home/.config/spectaclerc @@ -0,0 +1,6 @@ +[ImageSave] +lastImageSaveLocation=file:///home/alexion/Pictures/Screenshots/Screenshot_20260704_153951.png +translatedScreenshotsFolder=Screenshots + +[VideoSave] +translatedScreencastsFolder=Screencasts diff --git a/reference/home/.config/tmux/tmux.conf b/reference/home/.config/tmux/tmux.conf new file mode 100644 index 0000000..c478c78 --- /dev/null +++ b/reference/home/.config/tmux/tmux.conf @@ -0,0 +1,74 @@ +# Prefix: Ctrl-Space. Chosen over Ctrl-b (awkward reach) and Ctrl-a (collides +# with readline's beginning-of-line, which fights editing text in shells and +# in Claude Code's prompt). Verified clear of IME/KDE/Claude Code bindings. +unbind C-b +set -g prefix C-Space +bind C-Space send-prefix + +set -g mouse on +# OSC52 lets copy-mode selections land in the system clipboard via the +# terminal itself (Alacritty supports it) -- no wl-copy/xclip needed, and it +# still works over SSH later since the escape sequence travels with the data. +set -g set-clipboard on + +set -g mode-keys vi +set -g status-keys vi +bind -T copy-mode-vi v send -X begin-selection +bind -T copy-mode-vi y send -X copy-selection-and-cancel +bind -T copy-mode-vi MouseDragEnd1Pane send -X copy-selection-and-cancel + +# tmux's -h/-v split flags name the *arrangement*, not the divider line, which +# is backwards from how the divider looks -- so pick keys by what they draw: +# \ draws a side-by-side split (vertical line), - draws a stacked split +# (horizontal line). Unshifted versions of |/- since splitting is frequent. +unbind % +unbind '"' +bind \\ split-window -h -c "#{pane_current_path}" +bind - split-window -v -c "#{pane_current_path}" +bind c new-window -c "#{pane_current_path}" + +bind h select-pane -L +bind j select-pane -D +bind k select-pane -U +bind l select-pane -R + +set -g base-index 1 +setw -g pane-base-index 1 +set -g renumber-windows on + +bind r source-file ~/.config/tmux/tmux.conf \; display-message "tmux.conf reloaded" + +# True color passthrough. ",*" (rather than naming Alacritty's xterm-256color +# specifically) so this keeps working if the terminal emulator changes later. +set -g default-terminal "tmux-256color" +set -ag terminal-overrides ",*:RGB" + +# Default 500ms delay on Esc exists to disambiguate meta-key sequences; it +# reads as noticeable lag exiting insert mode in neovim, so drop it. +set -sg escape-time 10 + +set -g history-limit 10000 + +# Flag a background window in the status bar when its Claude Code session +# rings the terminal bell (permission prompt / task done while unfocused). +# bell-action=none stops tmux from ever passing the actual BEL through to +# Alacritty (no beep, no flash) -- monitor-bell's per-window tracking for the +# status-line highlight is independent of that and keeps working. +setw -g monitor-bell on +set -g bell-action none + +# Minimal status bar (session + window list only), styled to match the Nord +# theme already used in alacritty.toml. +set -g status-position bottom +set -g status-style "bg=#2E3440,fg=#D8DEE9" +set -g status-left " #S " +set -g status-left-length 20 +set -g status-right "" +setw -g window-status-current-style "bg=#88C0D0,fg=#2E3440,bold" +setw -g window-status-current-format " #I:#W " +setw -g window-status-format " #I:#W " +setw -g window-status-style "fg=#4C566A" +setw -g window-status-bell-style "bg=#BF616A,fg=#2E3440,bold" + +set -g pane-border-style "fg=#3B4252" +set -g pane-active-border-style "fg=#88C0D0" diff --git a/reference/home/.config/user-dirs.dirs b/reference/home/.config/user-dirs.dirs new file mode 100644 index 0000000..1b5a3ff --- /dev/null +++ b/reference/home/.config/user-dirs.dirs @@ -0,0 +1,16 @@ +# This file is written by xdg-user-dirs-update +# If you want to change or add directories, just edit the line you're +# interested in. All local changes will be retained on the next run. +# Format is XDG_xxx_DIR="$HOME/yyy", where yyy is a shell-escaped +# homedir-relative path, or XDG_xxx_DIR="/yyy", where /yyy is an +# absolute path. No other format is supported. +# +XDG_DESKTOP_DIR="$HOME/.desktop" +XDG_DOWNLOAD_DIR="$HOME/dwn" +XDG_TEMPLATES_DIR="$HOME/.ignoreme" +XDG_PUBLICSHARE_DIR="$HOME/.ignoreme" +XDG_DOCUMENTS_DIR="$HOME/doc" +XDG_MUSIC_DIR="$HOME/mus" +XDG_PICTURES_DIR="$HOME/pic" +XDG_VIDEOS_DIR="$HOME/vid" +XDG_PROJECTS_DIR="$HOME/wrk" diff --git a/reference/home/.config/xsettingsd/xsettingsd.conf b/reference/home/.config/xsettingsd/xsettingsd.conf new file mode 100644 index 0000000..6f1b3fd --- /dev/null +++ b/reference/home/.config/xsettingsd/xsettingsd.conf @@ -0,0 +1,17 @@ +Net/CursorBlinkTime 1000 +Net/CursorBlink 1 +Gdk/UnscaledDPI 98304 +Gdk/WindowScalingFactor 1 +Gtk/EnableAnimations 1 +Gtk/DecorationLayout "icon:minimize,maximize,close" +Net/ThemeName "Breeze-Dark" +Gtk/PrimaryButtonWarpsSlider 1 +Gtk/ToolbarStyle 3 +Gtk/MenuImages 1 +Gtk/ButtonImages 1 +Gtk/CursorThemeSize 24 +Gtk/CursorThemeName "breeze_cursors" +Net/SoundThemeName "ocean" +Net/IconThemeName "breeze-dark" +Gtk/FontName "Noto Sans, 14" + diff --git a/reference/home/.gitconfig b/reference/home/.gitconfig new file mode 100644 index 0000000..0e25d32 --- /dev/null +++ b/reference/home/.gitconfig @@ -0,0 +1,3 @@ +[user] + name = alexion + email = contact@alexion.dev diff --git a/reference/home/.gitignore b/reference/home/.gitignore new file mode 100644 index 0000000..d5cbde1 --- /dev/null +++ b/reference/home/.gitignore @@ -0,0 +1,8 @@ +.dotfiles +.DS_Store +*.swp +*.swo +*~ +Thumbs.db +**/__pycache__ +.config/fish/conf.d/secrets.fish diff --git a/reference/home/.gtkrc-2.0 b/reference/home/.gtkrc-2.0 new file mode 100644 index 0000000..53f3e06 --- /dev/null +++ b/reference/home/.gtkrc-2.0 @@ -0,0 +1,14 @@ +gtk-enable-animations=1 +gtk-theme-name="Breeze-Dark" +gtk-primary-button-warps-slider=1 +gtk-toolbar-style=3 +gtk-menu-images=1 +gtk-button-images=1 +gtk-cursor-blink-time=1000 +gtk-cursor-blink=1 +gtk-cursor-theme-size=24 +gtk-cursor-theme-name="breeze_cursors" +gtk-sound-theme-name="ocean" +gtk-icon-theme-name="breeze-dark" +gtk-font-name="Noto Sans, 14" + diff --git a/reference/home/.profile b/reference/home/.profile new file mode 100644 index 0000000..c2f3182 --- /dev/null +++ b/reference/home/.profile @@ -0,0 +1 @@ +. "$HOME/.cargo/env" diff --git a/reference/home/.zshenv b/reference/home/.zshenv new file mode 100644 index 0000000..c2f3182 --- /dev/null +++ b/reference/home/.zshenv @@ -0,0 +1 @@ +. "$HOME/.cargo/env" diff --git a/reference/home/.zshrc b/reference/home/.zshrc new file mode 100644 index 0000000..5c34b2c --- /dev/null +++ b/reference/home/.zshrc @@ -0,0 +1 @@ +source /usr/share/cachyos-zsh-config/cachyos-config.zsh -- 2.47.3 From 9b36cfadd66876c91d6302b316126dd4665be669 Mon Sep 17 00:00:00 2001 From: alexion Date: Sat, 18 Jul 2026 08:05:36 -0400 Subject: [PATCH 02/32] feat: build the Skeleton and a minimal neogaia Host MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Stand up the walking skeleton the rest of the laptop MVI extends and re-verifies against: the whole neogaia Host evaluates and its system toplevel builds (nix flake check green). - flake.nix: hand-rolled flake (no flake-parts). Base nixos-unstable, plus nixpkgs-unstable and nixos-25.05 for the per-package unstable/stable overlays, home-manager (nixpkgs followed), and chaotic-nyx (deliberately not following our nixpkgs, to keep its binary cache usable). checks build each Host toplevel. - lib/: trimmed helper lib — the Auto-loader (recursive .nix discovery, no null-placeholder hack), the host-builder, and the script-from-file helper. Deps inherited explicitly; no with lib.my, no nixosModules output. - system/: shared base config — the unstable/stable overlays, the user option (defaults to alexion, in wheel, drives system + home-manager user in lockstep), flakes, git, and home-manager as a NixOS module. - modules/example.nix: Auto-loader / Enable-convention reference Module, inert until enabled. - hosts/neogaia/: minimal laptop Host — placeholder filesystems, bootloader, and hardware profile. - CLAUDE.md: project agent instructions with a Gotchas section (nix on the CachyOS dev host, the chaotic overlay/cache behaviour, the Gitea CLI). --- .../tasks/0001-skeleton-and-building-host.md | 37 +++++ CLAUDE.md | 17 ++ flake.lock | 155 ++++++++++++++++++ flake.nix | 42 +++++ hosts/neogaia/default.nix | 26 +++ hosts/neogaia/hardware-configuration.nix | 20 +++ lib/default.nix | 83 ++++++++++ modules/example.nix | 14 ++ system/default.nix | 84 ++++++++++ 9 files changed, 478 insertions(+) create mode 100644 .claude/tasks/0001-skeleton-and-building-host.md create mode 100644 CLAUDE.md create mode 100644 flake.lock create mode 100644 flake.nix create mode 100644 hosts/neogaia/default.nix create mode 100644 hosts/neogaia/hardware-configuration.nix create mode 100644 lib/default.nix create mode 100644 modules/example.nix create mode 100644 system/default.nix diff --git a/.claude/tasks/0001-skeleton-and-building-host.md b/.claude/tasks/0001-skeleton-and-building-host.md new file mode 100644 index 0000000..2a61383 --- /dev/null +++ b/.claude/tasks/0001-skeleton-and-building-host.md @@ -0,0 +1,37 @@ +--- +spec: laptop-mvi +--- + +## What to build + +Stand up the `Skeleton` and a minimal `neogaia` `Host` that evaluates and whose system toplevel builds — the walking skeleton every later slice extends and re-verifies against. + +The `Skeleton` is a hand-rolled flake (no flake-parts): `nixos-unstable` base channel, an `unstable overlay` exposing `nixpkgs-unstable` as `unstable.`, a `stable overlay` exposing `nixos-25.05` as `stable.`, and chaotic-nyx wired as an input with its overlay and binary cache. +The helper lib is trimmed to three pieces: the `Auto-loader` (recursively discovers and imports every `Module` under `modules/` and every `Host` under `hosts/` with no null-placeholder traversal hack), the host-builder, and the script-from-file helper. +`with lib.my` is not used — dependencies are `inherit`ed explicitly. +The `Enable convention` uses the stdlib enable-option helper; every `Module` is imported unconditionally and guards its body with `mkIf config.modules..enable`. +home-manager is sourced from `nix-community` (master, nixpkgs followed) and integrated as a NixOS module with global packages and user packages. +The `user` is an explicit option defaulting to `alexion` (no impure environment lookup), placed in `wheel`, driving the system user and the home-manager user in lockstep. +The `neogaia` `Host` carries only enough (placeholder `hardware-configuration.nix`, filesystems/bootloader stubs, `stateVersion`) to make `nixosConfigurations.neogaia.config.system.build.toplevel` evaluate and build; real disk/kernel/networking arrive in later slices. + +Design the per-`Host` layout so disk layout, kernel, and channel are all per-`Host` concerns from the start (story 20), so the desktop and servers extend this foundation without restructuring. + +## Acceptance criteria + +- [x] `nix flake check` succeeds and the flake exposes `nixosConfigurations.neogaia`. +- [x] `nixosConfigurations.neogaia.config.system.build.toplevel` builds. +- [x] Adding a new `.nix` file under `modules/` is auto-discovered and imported without editing any `imports` list, and stays inert until its `enable` flag is set. +- [x] All three overlays resolve: `unstable.`, `stable.`, and a chaotic-nyx package are each reachable in a `Host`. +- [x] home-manager builds as part of the same `nixos-rebuild switch` toplevel (system + user environment atomic). +- [x] The `user` option defaults to `alexion`, has no impure environment lookup, places the user in `wheel`, and drives both the system and home-manager user. +- [x] The old `nixosModules` flake output and the `with lib.my` idiom are absent. + +## Implementation Notes + +- **Verification.** `nix flake check` builds `checks.x86_64-linux.neogaia` = the Host toplevel (the spec's primary seam). Overlays confirmed via `nix eval` of `pkgs.unstable.hello` (2.12.3), `pkgs.stable.hello` (2.12.1), and `pkgs.linuxPackages_cachyos.kernel` (7.1.3, from chaotic). Auto-loader inertness confirmed both ways: the reference `modules/example.nix` is off by default, and `extendModules` with `modules.example.enable = true` activates its body. +- **chaotic binary cache.** Wired via `inputs.chaotic.nixosModules.default`, which puts both the `nyx-cache.chaotic.cx` substituter and its trusted public key into the built config (verified by evaluating `config.nix.settings.substituters`/`trusted-public-keys`). chaotic deliberately does **not** follow our nixpkgs, so the cache stays usable. Making the substituter/key explicit is task 0003's concern; here it is inherited from the module. +- **Shared base lives in `system/`.** The Skeleton's shared base config (overlays, `user`, flakes, home-manager wiring) is a `system/` module always imported by the host-builder, kept separate from the auto-loaded feature `Module`s under `modules/` so the base is never gated by an `enable` flag. +- **`modules/example.nix` kept intentionally.** It is the Auto-loader / Enable-convention reference every real Module copies; remove it once a real Module supersedes its teaching value. +- **`scriptFromFile` present but unused.** The task mandates the helper lib carry it ("the script-from-file helper"); its first caller lands with a later Module. +- **Home-manager base user only.** The base sets `home.username`/`homeDirectory`/`stateVersion` for the `user`; `extraSpecialArgs` passes both `inputs` and `my` (the flake lib) so upcoming HM Modules (fish/tmux/nvim) can reach `scriptFromFile`. +- **Deviations from plan.** Added an `options.user.description` (GECOS) alongside `user.name` — small and expected for a real account. Baseline `git` + global `allowUnfree` are set in the base (git is required for flakes; unfree is needed by chaotic/home-manager and later Claude Code). Placeholder `fileSystems`/bootloader and `hardware-configuration.nix` in `neogaia` are stubs that task 0002 (disko) replaces. diff --git a/CLAUDE.md b/CLAUDE.md new file mode 100644 index 0000000..a4ab215 --- /dev/null +++ b/CLAUDE.md @@ -0,0 +1,17 @@ +# dotfiles-nixos + +One flake that builds every machine the user owns. +The domain model (Host, Module, Skeleton, Auto-loader, Enable convention, overlays) lives in `.claude/CONTEXT.md`; the current deliverable's spec is `.claude/spec/laptop-mvi.md`. + +## Gotchas + +- Nix on the dev host needs experimental features passed per-command. + This repo is developed on `neogaia` while it still runs **CachyOS** (the migration target), where Nix is the distro package at `/usr/bin/nix` in multi-user daemon mode. + The system `/etc/nix/nix.conf` does not enable flakes, so export `NIX_CONFIG="experimental-features = nix-command flakes"` (or pass `--extra-experimental-features 'nix-command flakes'`) for every command. +- The dev user is a non-trusted daemon client (`nix store info` reports `Trusted: 0`). + You cannot add substituters from the CLI, so rely on what the flake/config declares (e.g. the chaotic cache is wired by the chaotic module, not a CLI flag). +- If `/nix/store` is missing or `nix-daemon` is inactive after a fresh Nix install, initialise it with `sudo systemd-tmpfiles --create nix-daemon.conf && sudo systemctl enable --now nix-daemon.socket`. +- The primary build/verify seam for any Host is `nix flake check`, which builds `checks.x86_64-linux.` (the system toplevel); cheap targeted checks use `nix eval .#nixosConfigurations..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`); the forge CLI is `tea` (login `axi`), and `gh` is not installed. diff --git a/flake.lock b/flake.lock new file mode 100644 index 0000000..821e5bc --- /dev/null +++ b/flake.lock @@ -0,0 +1,155 @@ +{ + "nodes": { + "chaotic": { + "inputs": { + "flake-schemas": "flake-schemas", + "home-manager": "home-manager", + "nixpkgs": "nixpkgs" + }, + "locked": { + "lastModified": 1784318604, + "narHash": "sha256-P/N5ZbGWITiTfmiWpE/1uyXdOCagpgw/YAZLZJSzx/I=", + "owner": "chaotic-cx", + "repo": "nyx", + "rev": "21a8ef816f34558a438d778057a8809322ea2415", + "type": "github" + }, + "original": { + "owner": "chaotic-cx", + "ref": "nyxpkgs-unstable", + "repo": "nyx", + "type": "github" + } + }, + "flake-schemas": { + "locked": { + "lastModified": 1780327564, + "narHash": "sha256-HiRPtA0spK+Dkgbhz/1zW9glXxNVB+L4Rj2VYmdawb8=", + "rev": "6cc9bd98891b1fc6bb2b8cb3277df8bc72799ca6", + "revCount": 149, + "type": "tarball", + "url": "https://api.flakehub.com/f/pinned/DeterminateSystems/flake-schemas/0.5.0/019e83cf-9af3-78b1-ac5b-70e68ad1efe1/source.tar.gz" + }, + "original": { + "type": "tarball", + "url": "https://flakehub.com/f/DeterminateSystems/flake-schemas/%3D0.5.0.tar.gz" + } + }, + "home-manager": { + "inputs": { + "nixpkgs": [ + "chaotic", + "nixpkgs" + ] + }, + "locked": { + "lastModified": 1784129366, + "narHash": "sha256-N5JiyICSeQF14x+OQebNyPpYowOT9Rs1iKyeCylSzOA=", + "owner": "nix-community", + "repo": "home-manager", + "rev": "165228b0efefc3e635e5174020c40ea64271dc25", + "type": "github" + }, + "original": { + "owner": "nix-community", + "repo": "home-manager", + "type": "github" + } + }, + "home-manager_2": { + "inputs": { + "nixpkgs": [ + "nixpkgs" + ] + }, + "locked": { + "lastModified": 1784351324, + "narHash": "sha256-By+kuRJZRqs2TuXgtR8vJ8cTKWXw33YG/Yollu5cO1U=", + "owner": "nix-community", + "repo": "home-manager", + "rev": "460108009ca1ff69ca2ff19079ca2c838d6e3080", + "type": "github" + }, + "original": { + "owner": "nix-community", + "repo": "home-manager", + "type": "github" + } + }, + "nixpkgs": { + "locked": { + "lastModified": 1784120854, + "narHash": "sha256-KesHgItiZPgGX740axSiQLcIQ8D24MDqNpkKYWIek8k=", + "owner": "NixOS", + "repo": "nixpkgs", + "rev": "753cc8a3a87467296ddd1fa93f0cc3e81120ee46", + "type": "github" + }, + "original": { + "owner": "NixOS", + "ref": "nixos-unstable", + "repo": "nixpkgs", + "type": "github" + } + }, + "nixpkgs-stable": { + "locked": { + "lastModified": 1767313136, + "narHash": "sha256-16KkgfdYqjaeRGBaYsNrhPRRENs0qzkQVUooNHtoy2w=", + "owner": "nixos", + "repo": "nixpkgs", + "rev": "ac62194c3917d5f474c1a844b6fd6da2db95077d", + "type": "github" + }, + "original": { + "owner": "nixos", + "ref": "nixos-25.05", + "repo": "nixpkgs", + "type": "github" + } + }, + "nixpkgs-unstable": { + "locked": { + "lastModified": 1784347607, + "narHash": "sha256-VI5cdo27nEZ3m1SlgB8RvBbrqFUO2/dUgrrLWe407oA=", + "owner": "nixos", + "repo": "nixpkgs", + "rev": "31cd72fdba8fa052e437ce7e6879c4fe62def10f", + "type": "github" + }, + "original": { + "owner": "nixos", + "ref": "nixpkgs-unstable", + "repo": "nixpkgs", + "type": "github" + } + }, + "nixpkgs_2": { + "locked": { + "lastModified": 1784120854, + "narHash": "sha256-KesHgItiZPgGX740axSiQLcIQ8D24MDqNpkKYWIek8k=", + "owner": "nixos", + "repo": "nixpkgs", + "rev": "753cc8a3a87467296ddd1fa93f0cc3e81120ee46", + "type": "github" + }, + "original": { + "owner": "nixos", + "ref": "nixos-unstable", + "repo": "nixpkgs", + "type": "github" + } + }, + "root": { + "inputs": { + "chaotic": "chaotic", + "home-manager": "home-manager_2", + "nixpkgs": "nixpkgs_2", + "nixpkgs-stable": "nixpkgs-stable", + "nixpkgs-unstable": "nixpkgs-unstable" + } + } + }, + "root": "root", + "version": 7 +} diff --git a/flake.nix b/flake.nix new file mode 100644 index 0000000..4b733c5 --- /dev/null +++ b/flake.nix @@ -0,0 +1,42 @@ +{ + description = "Alexion's NixOS configuration — one flake for every Host"; + + inputs = { + # Base channel: nixos-unstable (rolling, but gated by the NixOS test suite). + nixpkgs.url = "github:nixos/nixpkgs/nixos-unstable"; + + # Fresher-than-base packages, reachable per-package as `unstable.`. + nixpkgs-unstable.url = "github:nixos/nixpkgs/nixpkgs-unstable"; + + # Latest stable release, reachable per-package as `stable.`. + nixpkgs-stable.url = "github:nixos/nixpkgs/nixos-25.05"; + + home-manager = { + url = "github:nix-community/home-manager"; + inputs.nixpkgs.follows = "nixpkgs"; + }; + + # CachyOS kernel + binary cache. Deliberately NOT following our nixpkgs, so the + # chaotic cache stays usable and the kernel is fetched rather than compiled. + chaotic.url = "github:chaotic-cx/nyx/nyxpkgs-unstable"; + }; + + outputs = + { self, nixpkgs, ... }@inputs: + let + inherit (nixpkgs) lib; + my = import ./lib { inherit lib inputs self; }; + in + { + # The trimmed helper lib: the Auto-loader, the host-builder, the script-from-file helper. + lib = my; + + # Every Host under hosts/ is auto-discovered and built. + nixosConfigurations = my.mkHosts (self + "/hosts"); + + # `nix flake check` builds each Host's toplevel — the primary test seam. + checks.x86_64-linux = lib.mapAttrs ( + _name: host: host.config.system.build.toplevel + ) self.nixosConfigurations; + }; +} diff --git a/hosts/neogaia/default.nix b/hosts/neogaia/default.nix new file mode 100644 index 0000000..ef88452 --- /dev/null +++ b/hosts/neogaia/default.nix @@ -0,0 +1,26 @@ +{ ... }: +# neogaia — Dell XPS 13 9380 laptop. +# +# Minimum viable Host: enough to evaluate and build the system toplevel. The +# real disk layout (disko: LUKS + btrfs + zram), the CachyOS kernel, networking, +# and the terminal Modules arrive in later tasks; the placeholders below are +# replaced by disko in task 0002. +{ + imports = [ ./hardware-configuration.nix ]; + + system.stateVersion = "25.05"; + + boot.loader.systemd-boot.enable = true; + boot.loader.efi.canTouchEfiVariables = true; + + # Placeholder filesystems so the toplevel builds; superseded by the disko + # layout in task 0002. + fileSystems."/" = { + device = "/dev/disk/by-label/nixos"; + fsType = "ext4"; + }; + fileSystems."/boot" = { + device = "/dev/disk/by-label/BOOT"; + fsType = "vfat"; + }; +} diff --git a/hosts/neogaia/hardware-configuration.nix b/hosts/neogaia/hardware-configuration.nix new file mode 100644 index 0000000..7585936 --- /dev/null +++ b/hosts/neogaia/hardware-configuration.nix @@ -0,0 +1,20 @@ +{ lib, modulesPath, ... }: +# Placeholder hardware profile — regenerated on the target machine at install +# time (`nixos-generate-config` / `disko-install`). Carries only enough for the +# toplevel to evaluate: the host platform and the XPS 13's initrd modules. +{ + imports = [ (modulesPath + "/installer/scan/not-detected.nix") ]; + + boot.initrd.availableKernelModules = [ + "xhci_pci" + "thunderbolt" + "nvme" + "usb_storage" + "sd_mod" + ]; + boot.initrd.kernelModules = [ ]; + boot.kernelModules = [ "kvm-intel" ]; + boot.extraModulePackages = [ ]; + + nixpkgs.hostPlatform = lib.mkDefault "x86_64-linux"; +} diff --git a/lib/default.nix b/lib/default.nix new file mode 100644 index 0000000..f6373d7 --- /dev/null +++ b/lib/default.nix @@ -0,0 +1,83 @@ +{ + lib, + inputs, + self, +}: +let + inherit (lib) + attrNames + filterAttrs + genAttrs + flatten + hasSuffix + mapAttrsToList + ; + + # --- Auto-loader --------------------------------------------------------- + # Recursively collect every `.nix` file under `dir`, returned as a flat list + # of paths suitable for a module `imports`. No null-placeholder traversal + # hack: a directory recurses, a `.nix` file is taken, anything else is skipped. + collectNixFiles = + dir: + flatten ( + mapAttrsToList ( + name: type: + let + path = dir + "/${name}"; + in + if type == "directory" then + collectNixFiles path + else if type == "regular" && hasSuffix ".nix" name then + [ path ] + else + [ ] + ) (builtins.readDir dir) + ); + + # --- Host-builder -------------------------------------------------------- + # Build one Host: every Module is imported unconditionally (inert until its + # `enable` flag is set), alongside home-manager, chaotic, the shared base, + # and the Host's own directory. + mkHost = + { + hostName, + system ? "x86_64-linux", + }: + inputs.nixpkgs.lib.nixosSystem { + inherit system; + specialArgs = { + inherit inputs; + my = self.lib; + }; + modules = + (collectNixFiles (self + "/modules")) + ++ [ + inputs.home-manager.nixosModules.home-manager + inputs.chaotic.nixosModules.default + (self + "/system") + (self + "/hosts/${hostName}") + { networking.hostName = hostName; } + ]; + }; + + # Discover every Host (a subdirectory of `hostsDir`) and build each one. + mkHosts = + hostsDir: + let + hostNames = attrNames (filterAttrs (_name: type: type == "directory") (builtins.readDir hostsDir)); + in + genAttrs hostNames (hostName: mkHost { inherit hostName; }); + + # --- Script-from-file helper -------------------------------------------- + # Turn a standalone script file into a package on PATH, keeping the script + # itself editable as a real file rather than an inlined heredoc. + scriptFromFile = pkgs: name: path: pkgs.writeShellScriptBin name (builtins.readFile path); +in +{ + inherit + collectNixFiles + mkHost + mkHosts + scriptFromFile + ; +} diff --git a/modules/example.nix b/modules/example.nix new file mode 100644 index 0000000..a149901 --- /dev/null +++ b/modules/example.nix @@ -0,0 +1,14 @@ +{ config, lib, ... }: +# The Auto-loader reference example. Every real Module copies this shape: it is +# imported unconditionally but its body stays inert until a Host sets the +# `enable` flag, so each Host reads as a checklist of `enable = true` lines. +let + cfg = config.modules.example; +in +{ + options.modules.example.enable = lib.mkEnableOption "the Auto-loader reference example Module"; + + config = lib.mkIf cfg.enable { + environment.etc."skeleton-example".text = "This Module is enabled.\n"; + }; +} diff --git a/system/default.nix b/system/default.nix new file mode 100644 index 0000000..74e601d --- /dev/null +++ b/system/default.nix @@ -0,0 +1,84 @@ +{ + config, + lib, + pkgs, + inputs, + ... +}: +# The Skeleton's shared base config: the pieces every Host carries regardless of +# which Modules it enables — overlays, the `user`, flakes, and home-manager. +let + inherit (lib) mkOption types; + user = config.user; + + # Instantiate an extra nixpkgs source for the same platform as the base pkgs. + pinArgs = prev: { + inherit (prev.stdenv.hostPlatform) system; + config.allowUnfree = true; + }; +in +{ + options.user = { + name = mkOption { + type = types.str; + default = "alexion"; + description = '' + The primary interactive user this Host is built for. An explicit option + with no impure environment lookup, so the config is reproducible and + honest about who the user is. Drives both the system account and the + home-manager user in lockstep. + ''; + }; + description = mkOption { + type = types.str; + default = "Alexion"; + description = "Human-readable description (GECOS field) for the primary user."; + }; + }; + + config = { + # Base is nixos-unstable; reach a package fresher with `unstable.` or + # pin it rock-solid with `stable.`. chaotic's overlay is added by its + # own NixOS module, imported by the host-builder. + nixpkgs.overlays = [ + (_final: prev: { + unstable = import inputs.nixpkgs-unstable (pinArgs prev); + stable = import inputs.nixpkgs-stable (pinArgs prev); + }) + ]; + nixpkgs.config.allowUnfree = true; + + # Flakes + a baseline so `nixos-rebuild switch` works from the console. + nix.settings.experimental-features = [ + "nix-command" + "flakes" + ]; + environment.systemPackages = [ pkgs.git ]; + + # Primary user, in wheel. The bootstrap password is set by hand at install + # time and never committed; moving it to a sops-backed hashedPasswordFile is + # the first post-boot task (out of scope for the MVI). + users.users.${user.name} = { + isNormalUser = true; + description = user.description; + extraGroups = [ "wheel" ]; + }; + + # home-manager as a NixOS module: one `nixos-rebuild switch` builds the + # system and the user environment atomically, sharing the system's pkgs + # (with our overlays) and installing user packages into the system profile. + home-manager = { + useGlobalPkgs = true; + useUserPackages = true; + extraSpecialArgs = { + inherit inputs; + my = inputs.self.lib; + }; + users.${user.name} = { + home.username = user.name; + home.homeDirectory = "/home/${user.name}"; + home.stateVersion = "25.05"; + }; + }; + }; +} -- 2.47.3 From 25e12f79def765249b78ef76daa3ccfa04485fbe Mon Sep 17 00:00:00 2001 From: alexion Date: Sat, 18 Jul 2026 10:26:02 -0400 Subject: [PATCH 03/32] build(deps): pin the stable overlay and stateVersion to 26.05 26.05 is the latest stable NixOS release as of now (26.11 is still the in-development branch that nixos-unstable reports); the previous 25.05 pin was a release behind. - flake.nix / flake.lock: stable overlay tracks nixos-26.05. - neogaia system.stateVersion and the base home.stateVersion set to 26.05 (fresh install, so aligning to the current stable release). - Refresh the "latest stable release" references in the project docs to match. Other inputs are rolling branches (nixos-unstable, nixpkgs-unstable, chaotic nyxpkgs-unstable) or master (home-manager, per spec), so they carry no version to bump. --- .claude/CONTEXT.md | 2 +- .claude/spec/laptop-mvi.md | 2 +- .claude/tasks/0001-skeleton-and-building-host.md | 2 +- flake.lock | 8 ++++---- flake.nix | 2 +- hosts/neogaia/default.nix | 2 +- system/default.nix | 2 +- 7 files changed, 10 insertions(+), 10 deletions(-) diff --git a/.claude/CONTEXT.md b/.claude/CONTEXT.md index edd69b9..df2ef03 100644 --- a/.claude/CONTEXT.md +++ b/.claude/CONTEXT.md @@ -29,5 +29,5 @@ The overlay exposing `nixpkgs-unstable` packages as `unstable.`, used to p _Avoid_: bleeding-edge, latest **stable overlay**: -The overlay exposing the latest stable release (`nixos-25.05`) as `stable.`, used to pin an individual package to the rock-solid release from the `nixos-unstable` base. +The overlay exposing the latest stable release (`nixos-26.05`) as `stable.`, used to pin an individual package to the rock-solid release from the `nixos-unstable` base. _Avoid_: LTS, release channel diff --git a/.claude/spec/laptop-mvi.md b/.claude/spec/laptop-mvi.md index f97bf43..364037c 100644 --- a/.claude/spec/laptop-mvi.md +++ b/.claude/spec/laptop-mvi.md @@ -46,7 +46,7 @@ The install itself is done from the NixOS live ISO by cloning the repo from my G - Hand-rolled flake, rewritten and trimmed; no flake-parts. - `Auto-loader` rewritten: recursively discovers and imports every `Module` under the modules tree without the old null-placeholder traversal hack; a single discovery helper feeds the `Host` imports. The old `nixosModules` flake output is dropped. - Helper lib trimmed to the `Auto-loader`, the host-builder, and the script-from-file helper. `with lib.my` replaced by explicit `inherit`s throughout. `enable` flags use the stdlib enable-option helper rather than bespoke sugar. -- `nixos-unstable` as the base channel. An `unstable overlay` exposes `nixpkgs-unstable` packages; a `stable overlay` exposes the latest stable release (`nixos-25.05`). chaotic-nyx added as an input with its overlay and binary cache from the start. +- `nixos-unstable` as the base channel. An `unstable overlay` exposes `nixpkgs-unstable` packages; a `stable overlay` exposes the latest stable release (`nixos-26.05`). chaotic-nyx added as an input with its overlay and binary cache from the start. - home-manager sourced from `nix-community`, tracking master with nixpkgs followed, integrated as a NixOS module with global packages and user packages. - User modelled as an explicit option defaulting to `alexion`, in `wheel`, driving the system user and the home-manager user in lockstep. diff --git a/.claude/tasks/0001-skeleton-and-building-host.md b/.claude/tasks/0001-skeleton-and-building-host.md index 2a61383..a2eefb4 100644 --- a/.claude/tasks/0001-skeleton-and-building-host.md +++ b/.claude/tasks/0001-skeleton-and-building-host.md @@ -6,7 +6,7 @@ spec: laptop-mvi Stand up the `Skeleton` and a minimal `neogaia` `Host` that evaluates and whose system toplevel builds — the walking skeleton every later slice extends and re-verifies against. -The `Skeleton` is a hand-rolled flake (no flake-parts): `nixos-unstable` base channel, an `unstable overlay` exposing `nixpkgs-unstable` as `unstable.`, a `stable overlay` exposing `nixos-25.05` as `stable.`, and chaotic-nyx wired as an input with its overlay and binary cache. +The `Skeleton` is a hand-rolled flake (no flake-parts): `nixos-unstable` base channel, an `unstable overlay` exposing `nixpkgs-unstable` as `unstable.`, a `stable overlay` exposing `nixos-26.05` as `stable.`, and chaotic-nyx wired as an input with its overlay and binary cache. The helper lib is trimmed to three pieces: the `Auto-loader` (recursively discovers and imports every `Module` under `modules/` and every `Host` under `hosts/` with no null-placeholder traversal hack), the host-builder, and the script-from-file helper. `with lib.my` is not used — dependencies are `inherit`ed explicitly. The `Enable convention` uses the stdlib enable-option helper; every `Module` is imported unconditionally and guards its body with `mkIf config.modules..enable`. diff --git a/flake.lock b/flake.lock index 821e5bc..088b99a 100644 --- a/flake.lock +++ b/flake.lock @@ -94,16 +94,16 @@ }, "nixpkgs-stable": { "locked": { - "lastModified": 1767313136, - "narHash": "sha256-16KkgfdYqjaeRGBaYsNrhPRRENs0qzkQVUooNHtoy2w=", + "lastModified": 1784280462, + "narHash": "sha256-DtoqIqM7VkR6NxAkcLpMwmi02USwWb3JdmNGLyhthc0=", "owner": "nixos", "repo": "nixpkgs", - "rev": "ac62194c3917d5f474c1a844b6fd6da2db95077d", + "rev": "293d6abedf0478e681a4dfcfcb35b30fc796a32f", "type": "github" }, "original": { "owner": "nixos", - "ref": "nixos-25.05", + "ref": "nixos-26.05", "repo": "nixpkgs", "type": "github" } diff --git a/flake.nix b/flake.nix index 4b733c5..bbad6ea 100644 --- a/flake.nix +++ b/flake.nix @@ -9,7 +9,7 @@ nixpkgs-unstable.url = "github:nixos/nixpkgs/nixpkgs-unstable"; # Latest stable release, reachable per-package as `stable.`. - nixpkgs-stable.url = "github:nixos/nixpkgs/nixos-25.05"; + nixpkgs-stable.url = "github:nixos/nixpkgs/nixos-26.05"; home-manager = { url = "github:nix-community/home-manager"; diff --git a/hosts/neogaia/default.nix b/hosts/neogaia/default.nix index ef88452..91dc728 100644 --- a/hosts/neogaia/default.nix +++ b/hosts/neogaia/default.nix @@ -8,7 +8,7 @@ { imports = [ ./hardware-configuration.nix ]; - system.stateVersion = "25.05"; + system.stateVersion = "26.05"; boot.loader.systemd-boot.enable = true; boot.loader.efi.canTouchEfiVariables = true; diff --git a/system/default.nix b/system/default.nix index 74e601d..f3cd0e2 100644 --- a/system/default.nix +++ b/system/default.nix @@ -77,7 +77,7 @@ in users.${user.name} = { home.username = user.name; home.homeDirectory = "/home/${user.name}"; - home.stateVersion = "25.05"; + home.stateVersion = "26.05"; }; }; }; -- 2.47.3 From cfe8d4ff9ffd36f52c076c16aff33b6a3662e1b4 Mon Sep 17 00:00:00 2001 From: alexion Date: Sat, 18 Jul 2026 11:13:47 -0400 Subject: [PATCH 04/32] style: scope in-file comments to the current file state In-file comments should describe only what the file currently is, not task numbers, external tooling, or past/future states. - hosts/neogaia: drop the "replaced by disko" / "arrive later" framing; describe the filesystems and hardware profile as the placeholder values they are. - lib: drop the "no null-placeholder traversal hack" comparison to a prior implementation. - system: drop "no impure environment lookup" from the user.name description and the sops/post-boot roadmap from the user comment. --- hosts/neogaia/default.nix | 9 +++------ hosts/neogaia/hardware-configuration.nix | 5 ++--- lib/default.nix | 4 ++-- system/default.nix | 10 +++------- 4 files changed, 10 insertions(+), 18 deletions(-) diff --git a/hosts/neogaia/default.nix b/hosts/neogaia/default.nix index 91dc728..4102e3f 100644 --- a/hosts/neogaia/default.nix +++ b/hosts/neogaia/default.nix @@ -1,10 +1,8 @@ { ... }: # neogaia — Dell XPS 13 9380 laptop. # -# Minimum viable Host: enough to evaluate and build the system toplevel. The -# real disk layout (disko: LUKS + btrfs + zram), the CachyOS kernel, networking, -# and the terminal Modules arrive in later tasks; the placeholders below are -# replaced by disko in task 0002. +# The filesystems and hardware profile below are placeholder values, not the +# machine's real encrypted layout. { imports = [ ./hardware-configuration.nix ]; @@ -13,8 +11,7 @@ boot.loader.systemd-boot.enable = true; boot.loader.efi.canTouchEfiVariables = true; - # Placeholder filesystems so the toplevel builds; superseded by the disko - # layout in task 0002. + # Placeholder label-based filesystems. fileSystems."/" = { device = "/dev/disk/by-label/nixos"; fsType = "ext4"; diff --git a/hosts/neogaia/hardware-configuration.nix b/hosts/neogaia/hardware-configuration.nix index 7585936..9735cf3 100644 --- a/hosts/neogaia/hardware-configuration.nix +++ b/hosts/neogaia/hardware-configuration.nix @@ -1,7 +1,6 @@ { lib, modulesPath, ... }: -# Placeholder hardware profile — regenerated on the target machine at install -# time (`nixos-generate-config` / `disko-install`). Carries only enough for the -# toplevel to evaluate: the host platform and the XPS 13's initrd modules. +# Placeholder hardware profile for the XPS 13: the host platform and the initrd +# modules the machine needs to evaluate and boot. { imports = [ (modulesPath + "/installer/scan/not-detected.nix") ]; diff --git a/lib/default.nix b/lib/default.nix index f6373d7..2a2de64 100644 --- a/lib/default.nix +++ b/lib/default.nix @@ -15,8 +15,8 @@ let # --- Auto-loader --------------------------------------------------------- # Recursively collect every `.nix` file under `dir`, returned as a flat list - # of paths suitable for a module `imports`. No null-placeholder traversal - # hack: a directory recurses, a `.nix` file is taken, anything else is skipped. + # of paths suitable for a module `imports`: a directory recurses, a `.nix` + # file is taken, anything else is skipped. collectNixFiles = dir: flatten ( diff --git a/system/default.nix b/system/default.nix index f3cd0e2..57f42b8 100644 --- a/system/default.nix +++ b/system/default.nix @@ -23,10 +23,8 @@ in type = types.str; default = "alexion"; description = '' - The primary interactive user this Host is built for. An explicit option - with no impure environment lookup, so the config is reproducible and - honest about who the user is. Drives both the system account and the - home-manager user in lockstep. + The primary interactive user this Host is built for. Drives both the + system account and the home-manager user in lockstep. ''; }; description = mkOption { @@ -55,9 +53,7 @@ in ]; environment.systemPackages = [ pkgs.git ]; - # Primary user, in wheel. The bootstrap password is set by hand at install - # time and never committed; moving it to a sops-backed hashedPasswordFile is - # the first post-boot task (out of scope for the MVI). + # Primary user, in wheel. No password is set here. users.users.${user.name} = { isNormalUser = true; description = user.description; -- 2.47.3 From da3037541365ad466dfbe51f422ea604a7754fb1 Mon Sep 17 00:00:00 2001 From: alexion Date: Sat, 18 Jul 2026 14:09:52 -0400 Subject: [PATCH 05/32] feat(neogaia): declare the encrypted disk with disko Add disko as a flake input (following nixpkgs) and wire its NixOS module into the host-builder, so any Host can declare a disk layout while the layout itself stays a per-Host concern. neogaia's layout (hosts/neogaia/disk.nix): one GPT NVMe disk with a systemd-boot EFI system partition and a LUKS container holding btrfs with @root/@home/@nix subvolumes. The initrd prompts for the passphrase on a normal boot. Swap is RAM-backed zram, so there is no on-disk swap partition; zramSwap is enabled directly for now. The placeholder fileSystems are removed since disko now derives them. --- .claude/tasks/0002-neogaia-disk-and-boot.md | 31 ++++++++++ flake.lock | 21 +++++++ flake.nix | 7 +++ hosts/neogaia/default.nix | 24 ++++---- hosts/neogaia/disk.nix | 66 +++++++++++++++++++++ lib/default.nix | 1 + 6 files changed, 138 insertions(+), 12 deletions(-) create mode 100644 .claude/tasks/0002-neogaia-disk-and-boot.md create mode 100644 hosts/neogaia/disk.nix diff --git a/.claude/tasks/0002-neogaia-disk-and-boot.md b/.claude/tasks/0002-neogaia-disk-and-boot.md new file mode 100644 index 0000000..4ab55b3 --- /dev/null +++ b/.claude/tasks/0002-neogaia-disk-and-boot.md @@ -0,0 +1,31 @@ +--- +spec: laptop-mvi +blocked-by: 0001-skeleton-and-building-host +--- + +## What to build + +Declare the `neogaia` laptop's disk with `disko` and make it unlock and boot on real hardware: a LUKS-encrypted btrfs volume with subvolumes plus zram swap, on an EFI system partition using systemd-boot, with the LUKS passphrase prompted at boot via the initrd. + +The layout must build from the same tree as the `Host` toplevel (so the whole-`Host` build exercises it), and must be expressed as a per-`Host` disk concern so other machines can declare their own layouts later. + +## Acceptance criteria + +- [x] `neogaia` declares a `disko` layout: LUKS-encrypted btrfs with subvolumes plus zram swap on an EFI system partition. +- [x] systemd-boot is the bootloader; the initrd prompts for the LUKS passphrase so a normal boot unlocks the encrypted disk. +- [x] The `disko` layout builds as part of the `neogaia` toplevel build (no separate invocation needed to catch layout errors). +- [x] The disk layout is a per-`Host` concern, expressible differently for future `Host`s without restructuring the `Skeleton`. + +## Implementation Notes + +- **Layout.** One GPT disk at `/dev/nvme0n1`: a 512M EF00 ESP (vfat, `umask=0077`) mounted at `/boot`, and a 100%-fill LUKS partition (`cryptroot`, `allowDiscards`) holding a btrfs filesystem with three subvolumes — `@root` → `/`, `@home` → `/home`, `@nix` → `/nix` — each mounted `compress=zstd,noatime`. + There is deliberately no on-disk swap partition; swap is RAM-backed zram. +- **Skeleton vs. per-Host split.** The disko *module* (`inputs.disko.nixosModules.disko`) is wired into the host-builder in `lib/default.nix`, so every `Host` can interpret a `disko.devices` declaration; the *layout itself* lives in `hosts/neogaia/disk.nix`. + A future `Host` declares a different layout, or none at all (an undeclared `disko.devices` is a no-op), so servers that preserve an existing pool by import need no `Skeleton` change. +- **disko input follows nixpkgs.** Unlike chaotic (which must not), disko follows our `nixpkgs` so it builds against the same base. +- **Boot unlock.** disko's `type = "luks"` (no key file) generates `boot.initrd.luks.devices.cryptroot`, so the classic initrd prompts for the passphrase on a normal boot; the `nvme` initrd module was already present in `hardware-configuration.nix`. +- **zram enabled directly, not yet a Module.** Criterion 1 requires "plus zram swap," so `zramSwap.enable = true` is set on the `Host` now. + Task 0003 owns the reusable zram toggle `Module` and will lift this line into it; the placeholder `fileSystems`/bootloader stubs from task 0001 are removed here since disko now derives `fileSystems`. +- **Verification.** `nix flake check` (the `checks.x86_64-linux.neogaia` toplevel) builds green. + Confirmed via `nix eval`: disko-derived `fileSystems` = `/`,`/home`,`/nix` on btrfs `/dev/mapper/cryptroot` + `/boot` on the ESP; `boot.initrd.luks.devices` = `["cryptroot"]`; `systemd-boot.enable` and `zramSwap.enable` both true; `swapDevices` empty. + The genuine end-to-end confirmation is the manual `disko-install` reimage, which is irreversible by nature and not automated. diff --git a/flake.lock b/flake.lock index 088b99a..c65b3e1 100644 --- a/flake.lock +++ b/flake.lock @@ -21,6 +21,26 @@ "type": "github" } }, + "disko": { + "inputs": { + "nixpkgs": [ + "nixpkgs" + ] + }, + "locked": { + "lastModified": 1781152676, + "narHash": "sha256-RxWs5ND31KzTG7wvMM+PMfUjyNpmIEr999lqNARaM5o=", + "owner": "nix-community", + "repo": "disko", + "rev": "ff8702b4de27f72b4c78573dfb89ec74e36abdf1", + "type": "github" + }, + "original": { + "owner": "nix-community", + "repo": "disko", + "type": "github" + } + }, "flake-schemas": { "locked": { "lastModified": 1780327564, @@ -143,6 +163,7 @@ "root": { "inputs": { "chaotic": "chaotic", + "disko": "disko", "home-manager": "home-manager_2", "nixpkgs": "nixpkgs_2", "nixpkgs-stable": "nixpkgs-stable", diff --git a/flake.nix b/flake.nix index bbad6ea..e4a224e 100644 --- a/flake.nix +++ b/flake.nix @@ -16,6 +16,13 @@ inputs.nixpkgs.follows = "nixpkgs"; }; + # Declarative disk partitioning. Each Host declares its own layout; a Host + # that preserves an existing pool simply declares none. + disko = { + url = "github:nix-community/disko"; + inputs.nixpkgs.follows = "nixpkgs"; + }; + # CachyOS kernel + binary cache. Deliberately NOT following our nixpkgs, so the # chaotic cache stays usable and the kernel is fetched rather than compiled. chaotic.url = "github:chaotic-cx/nyx/nyxpkgs-unstable"; diff --git a/hosts/neogaia/default.nix b/hosts/neogaia/default.nix index 4102e3f..e4f6858 100644 --- a/hosts/neogaia/default.nix +++ b/hosts/neogaia/default.nix @@ -1,23 +1,23 @@ { ... }: # neogaia — Dell XPS 13 9380 laptop. # -# The filesystems and hardware profile below are placeholder values, not the -# machine's real encrypted layout. +# The disk layout lives in ./disk.nix (disko); the resulting `fileSystems` are +# derived from it, so none are declared by hand here. { - imports = [ ./hardware-configuration.nix ]; + imports = [ + ./hardware-configuration.nix + ./disk.nix + ]; system.stateVersion = "26.05"; + # systemd-boot on the EFI system partition disko creates. The initrd prompts + # for the LUKS passphrase (disko wires up boot.initrd.luks.devices), so a + # normal boot unlocks the encrypted root. boot.loader.systemd-boot.enable = true; boot.loader.efi.canTouchEfiVariables = true; - # Placeholder label-based filesystems. - fileSystems."/" = { - device = "/dev/disk/by-label/nixos"; - fsType = "ext4"; - }; - fileSystems."/boot" = { - device = "/dev/disk/by-label/BOOT"; - fsType = "vfat"; - }; + # Swap is RAM-backed zram rather than an on-disk partition. Task 0003 lifts + # this into the zram toggle Module; enabled directly here for now. + zramSwap.enable = true; } diff --git a/hosts/neogaia/disk.nix b/hosts/neogaia/disk.nix new file mode 100644 index 0000000..024b062 --- /dev/null +++ b/hosts/neogaia/disk.nix @@ -0,0 +1,66 @@ +{ ... }: +# neogaia's disk layout, declared with disko and interpreted by the disko module +# the host-builder wires in. This is a per-Host concern: another Host declares a +# different `disko.devices` (or none, preserving an existing pool by importing it). +# +# One NVMe disk, GPT: an EFI system partition for systemd-boot, and a LUKS +# container holding a btrfs filesystem with subvolumes. Swap is zram (RAM-backed), +# so there is deliberately no on-disk swap partition. disko derives the matching +# `fileSystems.*` and `boot.initrd.luks.devices.*` from this, so a normal boot +# prompts for the passphrase in the initrd and unlocks the encrypted root. +{ + disko.devices.disk.main = { + type = "disk"; + device = "/dev/nvme0n1"; + content = { + type = "gpt"; + partitions = { + ESP = { + size = "512M"; + type = "EF00"; + content = { + type = "filesystem"; + format = "vfat"; + mountpoint = "/boot"; + mountOptions = [ "umask=0077" ]; + }; + }; + luks = { + size = "100%"; + content = { + type = "luks"; + name = "cryptroot"; + settings.allowDiscards = true; + content = { + type = "btrfs"; + extraArgs = [ "-f" ]; + subvolumes = { + "@root" = { + mountpoint = "/"; + mountOptions = [ + "compress=zstd" + "noatime" + ]; + }; + "@home" = { + mountpoint = "/home"; + mountOptions = [ + "compress=zstd" + "noatime" + ]; + }; + "@nix" = { + mountpoint = "/nix"; + mountOptions = [ + "compress=zstd" + "noatime" + ]; + }; + }; + }; + }; + }; + }; + }; + }; +} diff --git a/lib/default.nix b/lib/default.nix index 2a2de64..525c2be 100644 --- a/lib/default.nix +++ b/lib/default.nix @@ -54,6 +54,7 @@ let ++ [ inputs.home-manager.nixosModules.home-manager inputs.chaotic.nixosModules.default + inputs.disko.nixosModules.disko (self + "/system") (self + "/hosts/${hostName}") { networking.hostName = hostName; } -- 2.47.3 From bfc9e6f75b382ed53f50bcb3e4de28d2c5cb9bf1 Mon Sep 17 00:00:00 2001 From: alexion Date: Sat, 18 Jul 2026 14:21:09 -0400 Subject: [PATCH 06/32] style(neogaia): scope the hardware comments to the current file state --- hosts/neogaia/default.nix | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/hosts/neogaia/default.nix b/hosts/neogaia/default.nix index e4f6858..7547a35 100644 --- a/hosts/neogaia/default.nix +++ b/hosts/neogaia/default.nix @@ -17,7 +17,6 @@ boot.loader.systemd-boot.enable = true; boot.loader.efi.canTouchEfiVariables = true; - # Swap is RAM-backed zram rather than an on-disk partition. Task 0003 lifts - # this into the zram toggle Module; enabled directly here for now. + # Swap is RAM-backed zram rather than an on-disk partition. zramSwap.enable = true; } -- 2.47.3 From 053c4de5295c312f710a5f6d3dcbddd945c0bc29 Mon Sep 17 00:00:00 2001 From: alexion Date: Sat, 18 Jul 2026 15:28:39 -0400 Subject: [PATCH 07/32] feat(neogaia): add the CachyOS kernel and hardware enablement Select the CachyOS kernel per-Host via boot.kernelPackages, enable Intel microcode and redistributable firmware (ath10k for the QCA6174 wifi), and move zram behind a toggle Module. Declare the chaotic binary cache in the base Nix settings (extra-substituters/keys) so the built system fetches the kernel from nyx-cache rather than compiling it. --- .claude/tasks/0003-kernel-and-hardware.md | 27 +++++++++++++++++++++++ CLAUDE.md | 4 ++++ hosts/neogaia/default.nix | 19 +++++++++++++--- modules/zram.nix | 14 ++++++++++++ system/default.nix | 12 ++++++++++ 5 files changed, 73 insertions(+), 3 deletions(-) create mode 100644 .claude/tasks/0003-kernel-and-hardware.md create mode 100644 modules/zram.nix diff --git a/.claude/tasks/0003-kernel-and-hardware.md b/.claude/tasks/0003-kernel-and-hardware.md new file mode 100644 index 0000000..18b1e1e --- /dev/null +++ b/.claude/tasks/0003-kernel-and-hardware.md @@ -0,0 +1,27 @@ +--- +spec: laptop-mvi +blocked-by: 0001-skeleton-and-building-host +--- + +## What to build + +Give `neogaia` the kernel and hardware enablement it needs to run well on the Dell XPS 13 9380: the CachyOS kernel pulled as a binary from chaotic-nyx (not compiled from source), Intel microcode, and the redistributable firmware for the QCA6174 wifi. Add a zram toggle `Module` and enable it here. + +The kernel is selected through a small per-`Host` kernel mechanism so other `Host`s can choose different kernels. The chaotic substituter and its trusted public key are added to the Nix settings so the kernel is fetched from the binary cache from the first build. + +## Acceptance criteria + +- [x] `neogaia` runs the CachyOS kernel selected via a per-`Host` kernel mechanism, sourced from chaotic-nyx. +- [x] The chaotic substituter and trusted public key are in the Nix settings, so the kernel is fetched from cache rather than compiled. +- [x] Intel microcode is enabled. +- [x] Redistributable firmware is enabled so the QCA6174 wifi hardware is available. +- [x] A zram toggle `Module` exists (following the `Enable convention`) and is enabled on `neogaia`. +- [x] The `neogaia` toplevel still builds with all of the above. + +## Implementation Notes + +- **Per-`Host` kernel mechanism = native `boot.kernelPackages`.** neogaia sets `boot.kernelPackages = pkgs.linuxPackages_cachyos` directly in its Host directory (`hosts/neogaia/default.nix`). No custom wrapper option was added: `boot.kernelPackages` is already a per-`Host` setting, so other `Host`s pick their own kernel the same way. A string→package wrapper would have been premature abstraction with one `Host` and one kernel, so it was deliberately left out; the "mechanism" is the per-`Host` placement of the native option. +- **Substituter/key live in the shared base, via the `extra-` options.** They were added to `system/default.nix` (shared by every `Host`), not just neogaia, because the chaotic module is wired for all `Host`s and the cache is general plumbing. `nix.settings.extra-substituters` / `extra-trusted-public-keys` are used rather than the replacing `substituters` / `trusted-public-keys`, so `cache.nixos.org` (and any other substituter) is only appended to, never dropped. chaotic's own module also provides these entries; the explicit declaration is belt-and-suspenders and keeps the built system's cache config visible and independent of that module. +- **Dev-host build needed a daemon-level cache.** Building the toplevel here first compiled the CachyOS kernel (and rustc bootstrap) from source, because the build daemon's `/etc/nix/nix.conf` had no `nyx-cache` substituter — the built system's `nix.settings` do not govern the daemon doing the build, and the dev user is a non-trusted client that cannot add substituters from the CLI. Adding `extra-substituters`/`extra-trusted-public-keys` for `nyx-cache` to `/etc/nix/nix.conf` (sudo) and restarting `nix-daemon` fixed it; the build then fetched the kernel (7.1.3) from the cache. Recorded as a gotcha in `CLAUDE.md`. +- **zram `Module` kept minimal.** `modules/zram.nix` is a pure toggle (`zramSwap.enable = true` under `mkIf`), following the `modules/example.nix` reference shape. This preserves task 0002's exact behaviour while moving the switch behind the `Enable convention`; the direct `zramSwap.enable = true` previously inline in the `Host` was removed in favour of `modules.zram.enable = true`. +- **Why zram graduated to a `Module` but kernel/microcode/firmware stayed inline.** zram is a reusable, cross-`Host` feature toggle (the spec calls for it as a `Module`), whereas the kernel choice, Intel microcode, and firmware are neogaia-specific hardware facts that belong to the `Host` itself. diff --git a/CLAUDE.md b/CLAUDE.md index a4ab215..ce804ea 100644 --- a/CLAUDE.md +++ b/CLAUDE.md @@ -10,6 +10,10 @@ The domain model (Host, Module, Skeleton, Auto-loader, Enable convention, overla The system `/etc/nix/nix.conf` does not enable flakes, so export `NIX_CONFIG="experimental-features = nix-command flakes"` (or pass `--extra-experimental-features 'nix-command flakes'`) for every command. - The dev user is a non-trusted daemon client (`nix store info` reports `Trusted: 0`). You cannot add substituters from the CLI, so rely on what the flake/config declares (e.g. the chaotic cache is wired by the chaotic module, not a CLI flag). + Caveat that bites when a Host actually selects the CachyOS kernel: the substituters a `nix build` fetches from are the **daemon's** (`/etc/nix/nix.conf`), *not* the `nix.settings` of the config being built — those only govern the built system. + This dev host's `/etc/nix/nix.conf` has no `substituters`/`trusted-substituters` lines, so building a toplevel whose `boot.kernelPackages` is `linuxPackages_cachyos` compiles the kernel (and rustc bootstrap, etc.) from source instead of hitting `nyx-cache`. + To build such a Host here, first add `extra-substituters = https://nyx-cache.chaotic.cx/` and `extra-trusted-public-keys = nyx-cache.chaotic.cx:dJxTrgMC3V3cFfyIiBQDQorG6k1LsqurH/srpMSq7qk=` to `/etc/nix/nix.conf` (sudo) and `sudo systemctl restart nix-daemon`. + `nix eval` of the kernel version does *not* trigger this — only a real build does. - If `/nix/store` is missing or `nix-daemon` is inactive after a fresh Nix install, initialise it with `sudo systemd-tmpfiles --create nix-daemon.conf && sudo systemctl enable --now nix-daemon.socket`. - The primary build/verify seam for any Host is `nix flake check`, which builds `checks.x86_64-linux.` (the system toplevel); cheap targeted checks use `nix eval .#nixosConfigurations..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). diff --git a/hosts/neogaia/default.nix b/hosts/neogaia/default.nix index 7547a35..753d370 100644 --- a/hosts/neogaia/default.nix +++ b/hosts/neogaia/default.nix @@ -1,4 +1,4 @@ -{ ... }: +{ pkgs, ... }: # neogaia — Dell XPS 13 9380 laptop. # # The disk layout lives in ./disk.nix (disko); the resulting `fileSystems` are @@ -17,6 +17,19 @@ boot.loader.systemd-boot.enable = true; boot.loader.efi.canTouchEfiVariables = true; - # Swap is RAM-backed zram rather than an on-disk partition. - zramSwap.enable = true; + # Kernel is a per-Host choice, expressed through boot.kernelPackages: neogaia + # runs the CachyOS kernel from chaotic-nyx (fetched from the chaotic binary + # cache wired in system/, not compiled from source). Other Hosts pick their + # own kernel the same way, so the choice never leaves the Host. + boot.kernelPackages = pkgs.linuxPackages_cachyos; + + # Intel CPU microcode updates for the XPS 13's Core i7-8565U. + hardware.cpu.intel.updateMicrocode = true; + + # Redistributable firmware — carries the ath10k blobs the QCA6174 wifi needs. + hardware.enableRedistributableFirmware = true; + + # Swap is RAM-backed zram (the zram Module) rather than an on-disk partition, + # matching the disko layout, which declares no swap partition. + modules.zram.enable = true; } diff --git a/modules/zram.nix b/modules/zram.nix new file mode 100644 index 0000000..432544a --- /dev/null +++ b/modules/zram.nix @@ -0,0 +1,14 @@ +{ config, lib, ... }: +# A toggle for RAM-backed swap. A Host that has no on-disk swap partition (like +# neogaia, whose disko layout deliberately omits one) enables this to get a +# compressed zram device instead. +let + cfg = config.modules.zram; +in +{ + options.modules.zram.enable = lib.mkEnableOption "zram-backed compressed swap"; + + config = lib.mkIf cfg.enable { + zramSwap.enable = true; + }; +} diff --git a/system/default.nix b/system/default.nix index 57f42b8..1e828ce 100644 --- a/system/default.nix +++ b/system/default.nix @@ -51,6 +51,18 @@ in "nix-command" "flakes" ]; + + # The chaotic binary cache, declared explicitly on the built system so the + # CachyOS kernel is substituted rather than compiled. chaotic's own module + # also provides these, but stating them here keeps the built system's cache + # config visible and independent of that module. Added via the `extra-` + # options so they only append — cache.nixos.org and any other substituter + # are never dropped. (Fetching at install time depends on the installing + # daemon's substituters, not this — see the chaotic gotcha in CLAUDE.md.) + nix.settings.extra-substituters = [ "https://nyx-cache.chaotic.cx/" ]; + nix.settings.extra-trusted-public-keys = [ + "nyx-cache.chaotic.cx:dJxTrgMC3V3cFfyIiBQDQorG6k1LsqurH/srpMSq7qk=" + ]; environment.systemPackages = [ pkgs.git ]; # Primary user, in wheel. No password is set here. -- 2.47.3 From 8f83c3ca8c2cddc3f408fa58325c0f699c02ab7e Mon Sep 17 00:00:00 2001 From: alexion Date: Sat, 18 Jul 2026 15:41:43 -0400 Subject: [PATCH 08/32] refactor(neogaia): drop the zram Module and enable zram inline modules/zram.nix only wrapped the native zramSwap.enable toggle without adding anything. Rewrite the touched comments to describe only the current file content, and record the in-file-comment convention in CLAUDE.md. --- .claude/tasks/0003-kernel-and-hardware.md | 5 ++--- CLAUDE.md | 7 +++++++ hosts/neogaia/default.nix | 10 +++------- modules/zram.nix | 14 -------------- system/default.nix | 10 +++------- 5 files changed, 15 insertions(+), 31 deletions(-) delete mode 100644 modules/zram.nix diff --git a/.claude/tasks/0003-kernel-and-hardware.md b/.claude/tasks/0003-kernel-and-hardware.md index 18b1e1e..5a98a1b 100644 --- a/.claude/tasks/0003-kernel-and-hardware.md +++ b/.claude/tasks/0003-kernel-and-hardware.md @@ -15,7 +15,7 @@ The kernel is selected through a small per-`Host` kernel mechanism so other `Hos - [x] The chaotic substituter and trusted public key are in the Nix settings, so the kernel is fetched from cache rather than compiled. - [x] Intel microcode is enabled. - [x] Redistributable firmware is enabled so the QCA6174 wifi hardware is available. -- [x] A zram toggle `Module` exists (following the `Enable convention`) and is enabled on `neogaia`. +- [-] A zram toggle `Module` exists (following the `Enable convention`) and is enabled on `neogaia`. — Module dropped in PR review; zram is enabled inline on `neogaia` instead (see notes). - [x] The `neogaia` toplevel still builds with all of the above. ## Implementation Notes @@ -23,5 +23,4 @@ The kernel is selected through a small per-`Host` kernel mechanism so other `Hos - **Per-`Host` kernel mechanism = native `boot.kernelPackages`.** neogaia sets `boot.kernelPackages = pkgs.linuxPackages_cachyos` directly in its Host directory (`hosts/neogaia/default.nix`). No custom wrapper option was added: `boot.kernelPackages` is already a per-`Host` setting, so other `Host`s pick their own kernel the same way. A string→package wrapper would have been premature abstraction with one `Host` and one kernel, so it was deliberately left out; the "mechanism" is the per-`Host` placement of the native option. - **Substituter/key live in the shared base, via the `extra-` options.** They were added to `system/default.nix` (shared by every `Host`), not just neogaia, because the chaotic module is wired for all `Host`s and the cache is general plumbing. `nix.settings.extra-substituters` / `extra-trusted-public-keys` are used rather than the replacing `substituters` / `trusted-public-keys`, so `cache.nixos.org` (and any other substituter) is only appended to, never dropped. chaotic's own module also provides these entries; the explicit declaration is belt-and-suspenders and keeps the built system's cache config visible and independent of that module. - **Dev-host build needed a daemon-level cache.** Building the toplevel here first compiled the CachyOS kernel (and rustc bootstrap) from source, because the build daemon's `/etc/nix/nix.conf` had no `nyx-cache` substituter — the built system's `nix.settings` do not govern the daemon doing the build, and the dev user is a non-trusted client that cannot add substituters from the CLI. Adding `extra-substituters`/`extra-trusted-public-keys` for `nyx-cache` to `/etc/nix/nix.conf` (sudo) and restarting `nix-daemon` fixed it; the build then fetched the kernel (7.1.3) from the cache. Recorded as a gotcha in `CLAUDE.md`. -- **zram `Module` kept minimal.** `modules/zram.nix` is a pure toggle (`zramSwap.enable = true` under `mkIf`), following the `modules/example.nix` reference shape. This preserves task 0002's exact behaviour while moving the switch behind the `Enable convention`; the direct `zramSwap.enable = true` previously inline in the `Host` was removed in favour of `modules.zram.enable = true`. -- **Why zram graduated to a `Module` but kernel/microcode/firmware stayed inline.** zram is a reusable, cross-`Host` feature toggle (the spec calls for it as a `Module`), whereas the kernel choice, Intel microcode, and firmware are neogaia-specific hardware facts that belong to the `Host` itself. +- **zram is enabled inline, not as a `Module` (criterion 5 dropped).** The task asked for a zram toggle `Module`, and one was built first (`modules/zram.nix`), but PR review rejected it as a single-line abstraction that wraps the native `zramSwap.enable` toggle without adding anything. It was removed, and `neogaia` sets `zramSwap.enable = true` directly, as it did before task 0003. The `Enable convention` reference remains `modules/example.nix`; real feature `Module`s arrive with fish/tmux/nvim/Claude Code in later tasks. diff --git a/CLAUDE.md b/CLAUDE.md index ce804ea..905897c 100644 --- a/CLAUDE.md +++ b/CLAUDE.md @@ -3,6 +3,13 @@ One flake that builds every machine the user owns. The domain model (Host, Module, Skeleton, Auto-loader, Enable convention, overlays) lives in `.claude/CONTEXT.md`; the current deliverable's spec is `.claude/spec/laptop-mvi.md`. +## Conventions + +- In-file comments describe only the current content and behaviour of the file they sit in. + Do not write comments about history ("used to be X", "now moved here"), about how a value is consumed in other files, or that justify the choice against alternatives. + Never reference agent-facing state (anything under `.claude/` or `CLAUDE.md`) from a code comment: that state is not part of understanding the code. + A reader looking at only that file should find every comment accurate and self-contained. + ## Gotchas - Nix on the dev host needs experimental features passed per-command. diff --git a/hosts/neogaia/default.nix b/hosts/neogaia/default.nix index 753d370..7783987 100644 --- a/hosts/neogaia/default.nix +++ b/hosts/neogaia/default.nix @@ -17,10 +17,7 @@ boot.loader.systemd-boot.enable = true; boot.loader.efi.canTouchEfiVariables = true; - # Kernel is a per-Host choice, expressed through boot.kernelPackages: neogaia - # runs the CachyOS kernel from chaotic-nyx (fetched from the chaotic binary - # cache wired in system/, not compiled from source). Other Hosts pick their - # own kernel the same way, so the choice never leaves the Host. + # neogaia runs the CachyOS kernel, selected per-Host via boot.kernelPackages. boot.kernelPackages = pkgs.linuxPackages_cachyos; # Intel CPU microcode updates for the XPS 13's Core i7-8565U. @@ -29,7 +26,6 @@ # Redistributable firmware — carries the ath10k blobs the QCA6174 wifi needs. hardware.enableRedistributableFirmware = true; - # Swap is RAM-backed zram (the zram Module) rather than an on-disk partition, - # matching the disko layout, which declares no swap partition. - modules.zram.enable = true; + # Swap is RAM-backed zram rather than an on-disk partition. + zramSwap.enable = true; } diff --git a/modules/zram.nix b/modules/zram.nix deleted file mode 100644 index 432544a..0000000 --- a/modules/zram.nix +++ /dev/null @@ -1,14 +0,0 @@ -{ config, lib, ... }: -# A toggle for RAM-backed swap. A Host that has no on-disk swap partition (like -# neogaia, whose disko layout deliberately omits one) enables this to get a -# compressed zram device instead. -let - cfg = config.modules.zram; -in -{ - options.modules.zram.enable = lib.mkEnableOption "zram-backed compressed swap"; - - config = lib.mkIf cfg.enable { - zramSwap.enable = true; - }; -} diff --git a/system/default.nix b/system/default.nix index 1e828ce..2909c68 100644 --- a/system/default.nix +++ b/system/default.nix @@ -52,13 +52,9 @@ in "flakes" ]; - # The chaotic binary cache, declared explicitly on the built system so the - # CachyOS kernel is substituted rather than compiled. chaotic's own module - # also provides these, but stating them here keeps the built system's cache - # config visible and independent of that module. Added via the `extra-` - # options so they only append — cache.nixos.org and any other substituter - # are never dropped. (Fetching at install time depends on the installing - # daemon's substituters, not this — see the chaotic gotcha in CLAUDE.md.) + # The chaotic binary cache, so the CachyOS kernel is substituted rather than + # compiled. Appended with the `extra-` options so cache.nixos.org and any + # other substituter are kept alongside it. nix.settings.extra-substituters = [ "https://nyx-cache.chaotic.cx/" ]; nix.settings.extra-trusted-public-keys = [ "nyx-cache.chaotic.cx:dJxTrgMC3V3cFfyIiBQDQorG6k1LsqurH/srpMSq7qk=" -- 2.47.3 From 194d64dacdb1da8d7fa8e782a122406567c3c3da Mon Sep 17 00:00:00 2001 From: alexion Date: Sat, 18 Jul 2026 15:47:24 -0400 Subject: [PATCH 09/32] docs: require an agent sign-off on Gitea comments MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Add a convention: comments posted to Gitea go out under the operator's account, so sign each with a — Claude sign-off to attribute them to the agent. --- CLAUDE.md | 3 +++ 1 file changed, 3 insertions(+) diff --git a/CLAUDE.md b/CLAUDE.md index 905897c..72c7c35 100644 --- a/CLAUDE.md +++ b/CLAUDE.md @@ -9,6 +9,9 @@ The domain model (Host, Module, Skeleton, Auto-loader, Enable convention, overla Do not write comments about history ("used to be X", "now moved here"), about how a value is consumed in other files, or that justify the choice against alternatives. Never reference agent-facing state (anything under `.claude/` or `CLAUDE.md`) from a code comment: that state is not part of understanding the code. A reader looking at only that file should find every comment accurate and self-contained. +- 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.) ## Gotchas -- 2.47.3 From 20f5b33e00e23304d504e228ee9197a11cf6a8b6 Mon Sep 17 00:00:00 2001 From: alexion Date: Sat, 18 Jul 2026 15:56:58 -0400 Subject: [PATCH 10/32] feat(neogaia): enable networking and set the base locale Turn the booted laptop into a usable, remotely reachable console: enable NetworkManager so wifi can be joined from the console and an SSH daemon so the rest of the setup can be driven over the network. Set the base locale to timezone America/New_York, locale en_GB.UTF-8, and console keymap us. These live inline in the neogaia Host rather than behind enable Modules, matching the single-Host MVI precedent; a shared locale/networking Module can be extracted once a second Host needs the same settings. SSH keeps default password auth on, as first-boot access relies on the hand-set bootstrap password until key/sops-based auth lands post-install. --- .../tasks/0004-networking-and-base-system.md | 28 +++++++++++++++++++ hosts/neogaia/default.nix | 11 ++++++++ 2 files changed, 39 insertions(+) create mode 100644 .claude/tasks/0004-networking-and-base-system.md diff --git a/.claude/tasks/0004-networking-and-base-system.md b/.claude/tasks/0004-networking-and-base-system.md new file mode 100644 index 0000000..eebf868 --- /dev/null +++ b/.claude/tasks/0004-networking-and-base-system.md @@ -0,0 +1,28 @@ +--- +spec: laptop-mvi +blocked-by: 0001-skeleton-and-building-host +--- + +## What to build + +Make the booted laptop a usable console I can log into and reach remotely: NetworkManager for joining wifi, an SSH daemon for driving the rest of the setup over the network, and the base locale settings. + +Set timezone `America/New_York`, locale `en_GB.UTF-8`, and console keymap `us`. + +## Acceptance criteria + +- [x] NetworkManager is enabled so wifi can be joined from the console. +- [x] An SSH daemon is enabled so the machine can be driven remotely. +- [x] Timezone is `America/New_York`, locale is `en_GB.UTF-8`, console keymap is `us`. +- [x] The `neogaia` toplevel still builds with all of the above. + +## Implementation Notes + +- **Placement in the Host, not a Module.** NetworkManager, the SSH daemon, and the locale/timezone/keymap settings all live directly in `hosts/neogaia/default.nix`, alongside the kernel/hardware/zram lines from task 0003. + This follows the precedent set in that task, where a speculative enable-gated Module was dropped in review in favour of inlining for the single-Host MVI. + A shared locale Module or a networking Module can be extracted later when a second Host actually needs the same settings; extracting now would be speculative generality. +- **SSH left unhardened deliberately.** `services.openssh.enable = true` keeps NixOS's default password authentication on. + This is required by the install flow: first-boot access is over SSH with the hand-set bootstrap password, and no SSH keys or sops-derived age key exist until the install generates the Host's SSH host key. + Moving to key-only auth / `hashedPasswordFile` is the first post-boot follow-up per the spec's Secrets section, out of scope for the MVI. +- **Locale/timezone mix is as specified.** `i18n.defaultLocale = "en_GB.UTF-8"` with `time.timeZone = "America/New_York"` and `console.keyMap = "us"` mixes region and locale; this matches the operator's stated preferences verbatim and is intentional. +- **Verification.** Built the primary seam — `nix build .#checks.x86_64-linux.neogaia` (the Host toplevel) — to exit 0; the systemd units for `wpa_supplicant` (NetworkManager's backend) and openssh appear in the build. The five option values were also confirmed via `nix eval`. diff --git a/hosts/neogaia/default.nix b/hosts/neogaia/default.nix index 7783987..1170b15 100644 --- a/hosts/neogaia/default.nix +++ b/hosts/neogaia/default.nix @@ -28,4 +28,15 @@ # Swap is RAM-backed zram rather than an on-disk partition. zramSwap.enable = true; + + # NetworkManager drives the wifi so it can be joined from the console. + networking.networkmanager.enable = true; + + # An SSH daemon so the rest of the setup can be driven over the network. + services.openssh.enable = true; + + # Locale preferences for the base system. + time.timeZone = "America/New_York"; + i18n.defaultLocale = "en_GB.UTF-8"; + console.keyMap = "us"; } -- 2.47.3 From 8346d63e644bd99901f9536f9a0edb06750f9d2c Mon Sep 17 00:00:00 2001 From: alexion Date: Sat, 18 Jul 2026 16:25:11 -0400 Subject: [PATCH 11/32] feat(fish): add a native Module and set it as neogaia's login shell Translate the CachyOS fish snapshot into a home-manager Module: the fastfetch greeting, the bat-backed manpager, the done and bang-bang plugins (from nixpkgs, not a plugin manager), the history/backup/copy helper functions, and the eza and navigation aliases. Pacman-specific aliases are dropped or replaced with NixOS equivalents (update -> nixos-rebuild switch, cleanup -> nix-collect-garbage). Enabling the Module also makes fish the user's default login shell. --- .claude/tasks/0005-fish-shell-module.md | 34 ++++++ hosts/neogaia/default.nix | 3 + modules/fish.nix | 141 ++++++++++++++++++++++++ 3 files changed, 178 insertions(+) create mode 100644 .claude/tasks/0005-fish-shell-module.md create mode 100644 modules/fish.nix diff --git a/.claude/tasks/0005-fish-shell-module.md b/.claude/tasks/0005-fish-shell-module.md new file mode 100644 index 0000000..7c0ce56 --- /dev/null +++ b/.claude/tasks/0005-fish-shell-module.md @@ -0,0 +1,34 @@ +--- +spec: laptop-mvi +blocked-by: 0001-skeleton-and-building-host +--- + +## What to build + +A fish `Module`, configured natively via home-manager, that makes the shell feel like the current CachyOS setup but is correct for NixOS. Translate the existing `cachyos-config.fish` and the rest of the fish snapshot under `reference/home/.config/fish/`: the greeting, the bat-manpager, the `done` and bang-bang plugins, the helper functions, and the eza/nav aliases. Drop or replace every Arch/pacman-specific part with its NixOS equivalent. Set fish as the default login shell. + +Configure the plugins natively through home-manager rather than a fish plugin manager. + +## Acceptance criteria + +- [x] A fish `Module` (following the `Enable convention`) is enabled on `neogaia` and configured natively via home-manager. +- [x] The greeting, bat-manpager, `done` and bang-bang plugins, helper functions, and eza/nav aliases from the reference config are reproduced. +- [x] All Arch/pacman-specific parts are dropped or replaced with NixOS equivalents. +- [x] fish is the user's default login shell. +- [x] The `neogaia` toplevel still builds with the fish `Module` enabled. + +## Implementation Notes + +- **Plugins are native, not inlined.** `done` and `bang-bang` come from `pkgs.fishPlugins.*` via `programs.fish.plugins`; home-manager drops them into `~/.config/fish/conf.d/` where fish auto-sources them. + The reference config inlined the bang-bang `__history_previous_command` functions and binds by hand; the plugin supplies those, so they are not re-inlined. +- **`done` tuning uses `set -g`, not `set -U`.** The reference set `__done_min_cmd_duration`/`__done_notification_urgency_level` as universal variables, which persist to the universal-variable file and then ignore config changes. + A declarative config must own these each session, so they are set global (`set -g`) in `interactiveShellInit`. +- **Arch/pacman aliases dropped:** `grubup`, `fixpacman`, `mirror` (`cachyos-rate-mirrors`), `apt`/`apt-get` (`man pacman`), `big` (`expac`), `gitpkg`, `rip` (`expac`). + **Replaced with NixOS equivalents:** `update` → `sudo nixos-rebuild switch` (was `pacman -Syu`), `cleanup` → `sudo nix-collect-garbage -d` (was `pacman -Rns`). +- **Machine-specific PATH hacks dropped, per spec story 6 ("no impure environment lookup").** The hardcoded `BUN_INSTALL`, the `node-v24...` tarball path, and `~/Applications/depot_tools` from `config.fish` are absolute/impure and are not reproduced; such tooling should be Nix-provided when its own Module arrives. + The portable bits are kept: `~/.local/bin` on PATH (guarded) and sourcing `~/.fish_profile`. +- **`env.fish` and `rustup.fish` deliberately not carried over.** `ANDROID_HOME`/platform-tools (Android SDK) and `source ~/.cargo/env.fish` (rust) are dev-toolchain integrations outside the MVI's core tooling (fish/tmux/nvim/Claude Code); they belong to future per-toolchain Modules that provide those tools through Nix rather than sourcing an impure env file. +- **`hw` (`hwinfo --short`) and `tb` (`nc termbin.com 9999`) dropped.** These are generic rather than pacman-specific, but each needs an extra package (`hwinfo`, a `netcat`) that the minimal install does not otherwise pull in; left out of the MVI and easy to add later. +- **`copy` kept verbatim** (including the upstream `trim-right` call) to preserve exact parity with the current shell. +- **Verification.** The `neogaia` system toplevel builds (the spec's primary seam). + The rendered `~/.config/fish/` was inspected in the build output: aliases, the three helper functions, the `fastfetch` greeting, the bat manpager, the `done` tuning vars, and the `conf.d/plugin-done.fish` + `conf.d/plugin-bang-bang.fish` plugin files are all present; `users.users.alexion.shell` resolves to `pkgs.fish`. diff --git a/hosts/neogaia/default.nix b/hosts/neogaia/default.nix index 1170b15..49f8925 100644 --- a/hosts/neogaia/default.nix +++ b/hosts/neogaia/default.nix @@ -35,6 +35,9 @@ # An SSH daemon so the rest of the setup can be driven over the network. services.openssh.enable = true; + # fish as the login shell. + modules.fish.enable = true; + # Locale preferences for the base system. time.timeZone = "America/New_York"; i18n.defaultLocale = "en_GB.UTF-8"; diff --git a/modules/fish.nix b/modules/fish.nix new file mode 100644 index 0000000..a1ac01d --- /dev/null +++ b/modules/fish.nix @@ -0,0 +1,141 @@ +{ + config, + lib, + pkgs, + ... +}: +# fish for the primary user, configured natively through home-manager and set +# as their default login shell. Wires the done and bang-bang plugins, a +# fastfetch greeting, a bat-backed manpager, helper functions, and the eza and +# navigation aliases. +let + cfg = config.modules.fish; + user = config.user.name; +in +{ + options.modules.fish.enable = lib.mkEnableOption "fish as the user's shell, configured via home-manager"; + + config = lib.mkIf cfg.enable { + # System-level fish: registers it in /etc/shells and installs vendor + # completions. + programs.fish.enable = true; + users.users.${user}.shell = pkgs.fish; + + home-manager.users.${user} = { + home.packages = with pkgs; [ + eza + bat + fastfetch + wget + ]; + + programs.fish = { + enable = true; + + # done notifies when a long command finishes; bang-bang restores the !! + # and !$ history bindings. + plugins = [ + { + name = "done"; + src = pkgs.fishPlugins.done.src; + } + { + name = "bang-bang"; + src = pkgs.fishPlugins.bang-bang.src; + } + ]; + + shellAliases = { + # Replace ls with eza. + ls = "eza -al --color=always --group-directories-first --icons"; + la = "eza -a --color=always --group-directories-first --icons"; + ll = "eza -l --color=always --group-directories-first --icons"; + lt = "eza -aT --color=always --group-directories-first --icons"; + "l." = "eza -a | grep -e '^\\.'"; + + # Walk up the tree. + ".." = "cd .."; + "..." = "cd ../.."; + "...." = "cd ../../.."; + "....." = "cd ../../../.."; + "......" = "cd ../../../../.."; + + vi = "nvim"; + vim = "nvim"; + cp = "cp -v"; + tmx = "tmux new-session -A -s"; + + tarnow = "tar -acf "; + untar = "tar -zxvf "; + wget = "wget -c "; + psmem = "ps auxf | sort -nr -k 4"; + psmem10 = "ps auxf | sort -nr -k 4 | head -10"; + dir = "dir --color=auto"; + vdir = "vdir --color=auto"; + grep = "grep --color=auto"; + fgrep = "fgrep --color=auto"; + egrep = "egrep --color=auto"; + jctl = "journalctl -p 3 -xb"; + please = "sudo"; + + # Rebuild the system, and reclaim disk from old generations. + update = "sudo nixos-rebuild switch"; + cleanup = "sudo nix-collect-garbage -d"; + }; + + functions = { + # Run fastfetch as the welcome message. + fish_greeting = "fastfetch"; + + history = { + description = "Show command history with timestamps"; + body = "builtin history --show-time='%F %T '"; + }; + + backup = { + description = "Copy to .bak"; + argumentNames = "filename"; + body = "cp $filename $filename.bak"; + }; + + copy = { + description = "Copy a file, or recursively copy a source directory into a destination"; + body = '' + set count (count $argv | tr -d \n) + if test "$count" = 2; and test -d "$argv[1]" + set from (echo $argv[1] | trim-right /) + set to (echo $argv[2]) + command cp -r $from $to + else + command cp $argv + end + ''; + }; + }; + + interactiveShellInit = '' + set -gx EDITOR nvim + set -gx VISUAL nvim + + # Render man pages through bat. + set -x MANROFFOPT "-c" + set -x MANPAGER "sh -c 'col -bx | bat -l man -p'" + + # Tune the done plugin: only notify for commands past 10s, at low urgency. + set -g __done_min_cmd_duration 10000 + set -g __done_notification_urgency_level low + + # Prepend ~/.local/bin to PATH when it exists. + if test -d ~/.local/bin + fish_add_path ~/.local/bin + end + + # Apply fish-compatible profile overrides if present. + if test -f ~/.fish_profile + source ~/.fish_profile + end + ''; + }; + }; + }; +} -- 2.47.3 From 9eb0fe797a3430d81b6b79693d830c9face0ef9c Mon Sep 17 00:00:00 2001 From: alexion Date: Sat, 18 Jul 2026 18:17:48 -0400 Subject: [PATCH 12/32] feat(fish): gate the default shell and prefer abbreviations Move the login-shell switch behind a new modules.fish.defaultShell option (default false; neogaia opts in). Convert every non-eza alias to an abbreviation, set preferAbbrs and pin generateCompletions, and switch the command line to vi-style editing. Cap navigation at four dots and drop psmem/psmem10/dir/vdir/please. Comment the installed packages. --- .claude/tasks/0005-fish-shell-module.md | 7 ++++ hosts/neogaia/default.nix | 1 + modules/fish.nix | 50 ++++++++++++++++--------- 3 files changed, 40 insertions(+), 18 deletions(-) diff --git a/.claude/tasks/0005-fish-shell-module.md b/.claude/tasks/0005-fish-shell-module.md index 7c0ce56..4492e09 100644 --- a/.claude/tasks/0005-fish-shell-module.md +++ b/.claude/tasks/0005-fish-shell-module.md @@ -32,3 +32,10 @@ Configure the plugins natively through home-manager rather than a fish plugin ma - **`copy` kept verbatim** (including the upstream `trim-right` call) to preserve exact parity with the current shell. - **Verification.** The `neogaia` system toplevel builds (the spec's primary seam). The rendered `~/.config/fish/` was inspected in the build output: aliases, the three helper functions, the `fastfetch` greeting, the bat manpager, the `done` tuning vars, and the `conf.d/plugin-done.fish` + `conf.d/plugin-bang-bang.fish` plugin files are all present; `users.users.alexion.shell` resolves to `pkgs.fish`. + +### Post-review adjustments + +- **`defaultShell` option.** Setting fish as the login shell moved behind `modules.fish.defaultShell` (default `false`, gated with `mkIf`); `neogaia` opts in explicitly. Enabling the Module alone no longer changes the login shell. +- **Abbreviation-first.** Every non-eza alias is now a `shellAbbr` (the eza `ls` family stays an alias), `preferAbbrs = true`, and `generateCompletions = true` is pinned rather than left to the upstream default. +- **vi command-line editing.** `interactiveShellInit` sets `fish_key_bindings fish_vi_key_bindings`; the `bang-bang` plugin re-binds `!`/`$` in insert mode via its own `--on-variable fish_key_bindings` handler, so the switch keeps them working. +- **Trimmed aliases.** Navigation capped at four dots (`.....`/`......` dropped); `psmem`, `psmem10`, `dir`, `vdir`, and `please` removed. diff --git a/hosts/neogaia/default.nix b/hosts/neogaia/default.nix index 49f8925..3e5a605 100644 --- a/hosts/neogaia/default.nix +++ b/hosts/neogaia/default.nix @@ -37,6 +37,7 @@ # fish as the login shell. modules.fish.enable = true; + modules.fish.defaultShell = true; # Locale preferences for the base system. time.timeZone = "America/New_York"; diff --git a/modules/fish.nix b/modules/fish.nix index a1ac01d..f651254 100644 --- a/modules/fish.nix +++ b/modules/fish.nix @@ -4,34 +4,50 @@ pkgs, ... }: -# fish for the primary user, configured natively through home-manager and set -# as their default login shell. Wires the done and bang-bang plugins, a -# fastfetch greeting, a bat-backed manpager, helper functions, and the eza and -# navigation aliases. +# fish for the primary user, configured natively through home-manager. Wires the +# done and bang-bang plugins, a fastfetch greeting, a bat-backed manpager, helper +# functions, the eza aliases, and vi-style command-line editing. Set fish as the +# default login shell by also turning on `modules.fish.defaultShell`. let cfg = config.modules.fish; user = config.user.name; in { - options.modules.fish.enable = lib.mkEnableOption "fish as the user's shell, configured via home-manager"; + options.modules.fish = { + enable = lib.mkEnableOption "fish as the user's shell, configured via home-manager"; + + defaultShell = lib.mkOption { + type = lib.types.bool; + default = false; + description = "Set fish as the user's default login shell."; + }; + }; config = lib.mkIf cfg.enable { # System-level fish: registers it in /etc/shells and installs vendor # completions. programs.fish.enable = true; - users.users.${user}.shell = pkgs.fish; + users.users.${user}.shell = lib.mkIf cfg.defaultShell pkgs.fish; home-manager.users.${user} = { home.packages = with pkgs; [ - eza - bat - fastfetch - wget + eza # modern ls with git awareness and icons; backs the ls aliases + bat # syntax-highlighting cat/pager; backs the manpager below + fastfetch # system-info banner printed as the shell greeting + wget # non-interactive HTTP downloader; backs the wget abbreviation ]; programs.fish = { enable = true; + # Relied-on upstream defaults, pinned so a future change can't silently + # alter behaviour. + generateCompletions = true; + + # Prefer abbreviations over aliases when other modules wire up fish + # shortcuts, matching the abbreviation-first style below. + preferAbbrs = true; + # done notifies when a long command finishes; bang-bang restores the !! # and !$ history bindings. plugins = [ @@ -45,20 +61,20 @@ in } ]; + # Only the eza listings stay aliases; everything else is an abbreviation. shellAliases = { - # Replace ls with eza. ls = "eza -al --color=always --group-directories-first --icons"; la = "eza -a --color=always --group-directories-first --icons"; ll = "eza -l --color=always --group-directories-first --icons"; lt = "eza -aT --color=always --group-directories-first --icons"; "l." = "eza -a | grep -e '^\\.'"; + }; + shellAbbrs = { # Walk up the tree. ".." = "cd .."; "..." = "cd ../.."; "...." = "cd ../../.."; - "....." = "cd ../../../.."; - "......" = "cd ../../../../.."; vi = "nvim"; vim = "nvim"; @@ -68,15 +84,10 @@ in tarnow = "tar -acf "; untar = "tar -zxvf "; wget = "wget -c "; - psmem = "ps auxf | sort -nr -k 4"; - psmem10 = "ps auxf | sort -nr -k 4 | head -10"; - dir = "dir --color=auto"; - vdir = "vdir --color=auto"; grep = "grep --color=auto"; fgrep = "fgrep --color=auto"; egrep = "egrep --color=auto"; jctl = "journalctl -p 3 -xb"; - please = "sudo"; # Rebuild the system, and reclaim disk from old generations. update = "sudo nixos-rebuild switch"; @@ -114,6 +125,9 @@ in }; interactiveShellInit = '' + # vi-style modal editing on the command line. + set -g fish_key_bindings fish_vi_key_bindings + set -gx EDITOR nvim set -gx VISUAL nvim -- 2.47.3 From 16f29bd64a806b852de3098275380c01398a6fb3 Mon Sep 17 00:00:00 2001 From: alexion Date: Sat, 18 Jul 2026 18:34:42 -0400 Subject: [PATCH 13/32] refactor(fish): read the non-trivial snippets from files Move the interactiveShellInit block and the copy function body into modules/fish/*.fish and pull them in with builtins.readFile, so the non-trivial fish lives in real fish files (highlighting, fish_indent) instead of inline Nix strings. The Auto-loader only collects .nix, so the new .fish files are ignored by it. Rendered config is unchanged. --- modules/fish.nix | 37 ++---------------------------- modules/fish/copy.fish | 8 +++++++ modules/fish/interactive-init.fish | 23 +++++++++++++++++++ 3 files changed, 33 insertions(+), 35 deletions(-) create mode 100644 modules/fish/copy.fish create mode 100644 modules/fish/interactive-init.fish diff --git a/modules/fish.nix b/modules/fish.nix index f651254..3b546b8 100644 --- a/modules/fish.nix +++ b/modules/fish.nix @@ -111,44 +111,11 @@ in copy = { description = "Copy a file, or recursively copy a source directory into a destination"; - body = '' - set count (count $argv | tr -d \n) - if test "$count" = 2; and test -d "$argv[1]" - set from (echo $argv[1] | trim-right /) - set to (echo $argv[2]) - command cp -r $from $to - else - command cp $argv - end - ''; + body = builtins.readFile ./fish/copy.fish; }; }; - interactiveShellInit = '' - # vi-style modal editing on the command line. - set -g fish_key_bindings fish_vi_key_bindings - - set -gx EDITOR nvim - set -gx VISUAL nvim - - # Render man pages through bat. - set -x MANROFFOPT "-c" - set -x MANPAGER "sh -c 'col -bx | bat -l man -p'" - - # Tune the done plugin: only notify for commands past 10s, at low urgency. - set -g __done_min_cmd_duration 10000 - set -g __done_notification_urgency_level low - - # Prepend ~/.local/bin to PATH when it exists. - if test -d ~/.local/bin - fish_add_path ~/.local/bin - end - - # Apply fish-compatible profile overrides if present. - if test -f ~/.fish_profile - source ~/.fish_profile - end - ''; + interactiveShellInit = builtins.readFile ./fish/interactive-init.fish; }; }; }; diff --git a/modules/fish/copy.fish b/modules/fish/copy.fish new file mode 100644 index 0000000..15b17a4 --- /dev/null +++ b/modules/fish/copy.fish @@ -0,0 +1,8 @@ +set count (count $argv | tr -d \n) +if test "$count" = 2; and test -d "$argv[1]" + set from (echo $argv[1] | trim-right /) + set to (echo $argv[2]) + command cp -r $from $to +else + command cp $argv +end diff --git a/modules/fish/interactive-init.fish b/modules/fish/interactive-init.fish new file mode 100644 index 0000000..08d8b9e --- /dev/null +++ b/modules/fish/interactive-init.fish @@ -0,0 +1,23 @@ +# vi-style modal editing on the command line. +set -g fish_key_bindings fish_vi_key_bindings + +set -gx EDITOR nvim +set -gx VISUAL nvim + +# Render man pages through bat. +set -x MANROFFOPT "-c" +set -x MANPAGER "sh -c 'col -bx | bat -l man -p'" + +# Tune the done plugin: only notify for commands past 10s, at low urgency. +set -g __done_min_cmd_duration 10000 +set -g __done_notification_urgency_level low + +# Prepend ~/.local/bin to PATH when it exists. +if test -d ~/.local/bin + fish_add_path ~/.local/bin +end + +# Apply fish-compatible profile overrides if present. +if test -f ~/.fish_profile + source ~/.fish_profile +end -- 2.47.3 From b91e434c87d0494b56975b5e97d9c0f9fa897f09 Mon Sep 17 00:00:00 2001 From: alexion Date: Sat, 18 Jul 2026 18:35:21 -0400 Subject: [PATCH 14/32] docs(fish): record the file-extraction in the project notes --- .claude/tasks/0005-fish-shell-module.md | 1 + 1 file changed, 1 insertion(+) diff --git a/.claude/tasks/0005-fish-shell-module.md b/.claude/tasks/0005-fish-shell-module.md index 4492e09..389c425 100644 --- a/.claude/tasks/0005-fish-shell-module.md +++ b/.claude/tasks/0005-fish-shell-module.md @@ -39,3 +39,4 @@ Configure the plugins natively through home-manager rather than a fish plugin ma - **Abbreviation-first.** Every non-eza alias is now a `shellAbbr` (the eza `ls` family stays an alias), `preferAbbrs = true`, and `generateCompletions = true` is pinned rather than left to the upstream default. - **vi command-line editing.** `interactiveShellInit` sets `fish_key_bindings fish_vi_key_bindings`; the `bang-bang` plugin re-binds `!`/`$` in insert mode via its own `--on-variable fish_key_bindings` handler, so the switch keeps them working. - **Trimmed aliases.** Navigation capped at four dots (`.....`/`......` dropped); `psmem`, `psmem10`, `dir`, `vdir`, and `please` removed. +- **Non-trivial fish read from files.** The `interactiveShellInit` block and the `copy` function body live in `modules/fish/interactive-init.fish` and `modules/fish/copy.fish`, pulled in with `builtins.readFile`, so the substantial fish is editable as real fish files. The trivial one-liners stay inline. The Auto-loader only collects `.nix`, so the `.fish` files are inert to it, and the rendered config is byte-for-byte identical to the inline version. -- 2.47.3 From 5a89d4addbcd0e9a036dd2f80c71aaed7d1647e0 Mon Sep 17 00:00:00 2001 From: alexion Date: Sat, 18 Jul 2026 18:45:06 -0400 Subject: [PATCH 15/32] refactor(fish): lay the Module out like ~/.config/fish Move the Module to modules/fish/fish.nix and mirror a real fish config directory beside it: config.fish (interactive init), conf.d/done.fish (the done plugin tuning, now a real conf.d file), and functions/copy.fish. completions/ and themes/ are omitted as they have no content and git cannot track empty directories. --- .claude/tasks/0005-fish-shell-module.md | 2 +- modules/fish/conf.d/done.fish | 3 +++ modules/fish/{interactive-init.fish => config.fish} | 4 ---- modules/{ => fish}/fish.nix | 7 +++++-- modules/fish/{ => functions}/copy.fish | 0 5 files changed, 9 insertions(+), 7 deletions(-) create mode 100644 modules/fish/conf.d/done.fish rename modules/fish/{interactive-init.fish => config.fish} (74%) rename modules/{ => fish}/fish.nix (93%) rename modules/fish/{ => functions}/copy.fish (100%) diff --git a/.claude/tasks/0005-fish-shell-module.md b/.claude/tasks/0005-fish-shell-module.md index 389c425..136ac72 100644 --- a/.claude/tasks/0005-fish-shell-module.md +++ b/.claude/tasks/0005-fish-shell-module.md @@ -39,4 +39,4 @@ Configure the plugins natively through home-manager rather than a fish plugin ma - **Abbreviation-first.** Every non-eza alias is now a `shellAbbr` (the eza `ls` family stays an alias), `preferAbbrs = true`, and `generateCompletions = true` is pinned rather than left to the upstream default. - **vi command-line editing.** `interactiveShellInit` sets `fish_key_bindings fish_vi_key_bindings`; the `bang-bang` plugin re-binds `!`/`$` in insert mode via its own `--on-variable fish_key_bindings` handler, so the switch keeps them working. - **Trimmed aliases.** Navigation capped at four dots (`.....`/`......` dropped); `psmem`, `psmem10`, `dir`, `vdir`, and `please` removed. -- **Non-trivial fish read from files.** The `interactiveShellInit` block and the `copy` function body live in `modules/fish/interactive-init.fish` and `modules/fish/copy.fish`, pulled in with `builtins.readFile`, so the substantial fish is editable as real fish files. The trivial one-liners stay inline. The Auto-loader only collects `.nix`, so the `.fish` files are inert to it, and the rendered config is byte-for-byte identical to the inline version. +- **Module directory mirrors `~/.config/fish/`.** The Module lives at `modules/fish/fish.nix` with its hand-written fish laid out as in a real fish config: `config.fish` (interactive init, read into `interactiveShellInit`), `conf.d/done.fish` (the done plugin's tuning, placed as a real conf.d file via `xdg.configFile`), and `functions/copy.fish` (the non-trivial `copy` body, read into the `functions` option). Trivial one-liner functions stay inline in `fish.nix`. `completions/` and `themes/` are omitted because they have no content and git cannot track empty directories. The Auto-loader only collects `.nix`, so every `.fish` file is inert to it. diff --git a/modules/fish/conf.d/done.fish b/modules/fish/conf.d/done.fish new file mode 100644 index 0000000..ff0e81e --- /dev/null +++ b/modules/fish/conf.d/done.fish @@ -0,0 +1,3 @@ +# Tune the done plugin: only notify for commands past 10s, at low urgency. +set -g __done_min_cmd_duration 10000 +set -g __done_notification_urgency_level low diff --git a/modules/fish/interactive-init.fish b/modules/fish/config.fish similarity index 74% rename from modules/fish/interactive-init.fish rename to modules/fish/config.fish index 08d8b9e..ceb444a 100644 --- a/modules/fish/interactive-init.fish +++ b/modules/fish/config.fish @@ -8,10 +8,6 @@ set -gx VISUAL nvim set -x MANROFFOPT "-c" set -x MANPAGER "sh -c 'col -bx | bat -l man -p'" -# Tune the done plugin: only notify for commands past 10s, at low urgency. -set -g __done_min_cmd_duration 10000 -set -g __done_notification_urgency_level low - # Prepend ~/.local/bin to PATH when it exists. if test -d ~/.local/bin fish_add_path ~/.local/bin diff --git a/modules/fish.nix b/modules/fish/fish.nix similarity index 93% rename from modules/fish.nix rename to modules/fish/fish.nix index 3b546b8..535c39b 100644 --- a/modules/fish.nix +++ b/modules/fish/fish.nix @@ -37,6 +37,9 @@ in wget # non-interactive HTTP downloader; backs the wget abbreviation ]; + # done's tuning lives in its own conf.d snippet, mirroring ~/.config/fish. + xdg.configFile."fish/conf.d/done.fish".source = ./conf.d/done.fish; + programs.fish = { enable = true; @@ -111,11 +114,11 @@ in copy = { description = "Copy a file, or recursively copy a source directory into a destination"; - body = builtins.readFile ./fish/copy.fish; + body = builtins.readFile ./functions/copy.fish; }; }; - interactiveShellInit = builtins.readFile ./fish/interactive-init.fish; + interactiveShellInit = builtins.readFile ./config.fish; }; }; }; diff --git a/modules/fish/copy.fish b/modules/fish/functions/copy.fish similarity index 100% rename from modules/fish/copy.fish rename to modules/fish/functions/copy.fish -- 2.47.3 From 95869fb11f9095ec99d6701b5c097ae4b4297e20 Mon Sep 17 00:00:00 2001 From: alexion Date: Sat, 18 Jul 2026 18:56:07 -0400 Subject: [PATCH 16/32] refactor(fish): assemble the interactive init in Nix, not at runtime Fold the done plugin tuning back into config.fish and drop the conf.d fragment: config.fish is read whole into interactiveShellInit, so Nix assembles the interactive init at build time instead of fish autoloading a separate conf.d file. functions/copy.fish stays a function file, the idiomatic home for a lazily-autoloaded function. --- .claude/tasks/0005-fish-shell-module.md | 2 +- modules/fish/conf.d/done.fish | 3 --- modules/fish/config.fish | 4 ++++ modules/fish/fish.nix | 3 --- 4 files changed, 5 insertions(+), 7 deletions(-) delete mode 100644 modules/fish/conf.d/done.fish diff --git a/.claude/tasks/0005-fish-shell-module.md b/.claude/tasks/0005-fish-shell-module.md index 136ac72..6bf2c20 100644 --- a/.claude/tasks/0005-fish-shell-module.md +++ b/.claude/tasks/0005-fish-shell-module.md @@ -39,4 +39,4 @@ Configure the plugins natively through home-manager rather than a fish plugin ma - **Abbreviation-first.** Every non-eza alias is now a `shellAbbr` (the eza `ls` family stays an alias), `preferAbbrs = true`, and `generateCompletions = true` is pinned rather than left to the upstream default. - **vi command-line editing.** `interactiveShellInit` sets `fish_key_bindings fish_vi_key_bindings`; the `bang-bang` plugin re-binds `!`/`$` in insert mode via its own `--on-variable fish_key_bindings` handler, so the switch keeps them working. - **Trimmed aliases.** Navigation capped at four dots (`.....`/`......` dropped); `psmem`, `psmem10`, `dir`, `vdir`, and `please` removed. -- **Module directory mirrors `~/.config/fish/`.** The Module lives at `modules/fish/fish.nix` with its hand-written fish laid out as in a real fish config: `config.fish` (interactive init, read into `interactiveShellInit`), `conf.d/done.fish` (the done plugin's tuning, placed as a real conf.d file via `xdg.configFile`), and `functions/copy.fish` (the non-trivial `copy` body, read into the `functions` option). Trivial one-liner functions stay inline in `fish.nix`. `completions/` and `themes/` are omitted because they have no content and git cannot track empty directories. The Auto-loader only collects `.nix`, so every `.fish` file is inert to it. +- **Module directory mirrors `~/.config/fish/`.** The Module lives at `modules/fish/fish.nix` with its hand-written fish laid out as in a real fish config: `config.fish` (the whole interactive init, read into `interactiveShellInit`) and `functions/copy.fish` (the non-trivial `copy` body, read into the `functions` option). Trivial one-liner functions stay inline in `fish.nix`. Everything Nix assembles at build time — nothing of ours is autoloaded from a separate runtime file — so the `done` plugin tuning stays inside `config.fish` rather than a `conf.d` fragment (`conf.d` would only earn its name if fish autoloaded it at runtime). `functions/copy.fish` is the sole exception, because fish autoloads function files lazily and that is the idiomatic home for a function. `completions/`/`themes/`/`conf.d/` are omitted as they hold no content of ours and git cannot track empty directories. The Auto-loader only collects `.nix`, so every `.fish` file is inert to it. diff --git a/modules/fish/conf.d/done.fish b/modules/fish/conf.d/done.fish deleted file mode 100644 index ff0e81e..0000000 --- a/modules/fish/conf.d/done.fish +++ /dev/null @@ -1,3 +0,0 @@ -# Tune the done plugin: only notify for commands past 10s, at low urgency. -set -g __done_min_cmd_duration 10000 -set -g __done_notification_urgency_level low diff --git a/modules/fish/config.fish b/modules/fish/config.fish index ceb444a..08d8b9e 100644 --- a/modules/fish/config.fish +++ b/modules/fish/config.fish @@ -8,6 +8,10 @@ set -gx VISUAL nvim set -x MANROFFOPT "-c" set -x MANPAGER "sh -c 'col -bx | bat -l man -p'" +# Tune the done plugin: only notify for commands past 10s, at low urgency. +set -g __done_min_cmd_duration 10000 +set -g __done_notification_urgency_level low + # Prepend ~/.local/bin to PATH when it exists. if test -d ~/.local/bin fish_add_path ~/.local/bin diff --git a/modules/fish/fish.nix b/modules/fish/fish.nix index 535c39b..4a73897 100644 --- a/modules/fish/fish.nix +++ b/modules/fish/fish.nix @@ -37,9 +37,6 @@ in wget # non-interactive HTTP downloader; backs the wget abbreviation ]; - # done's tuning lives in its own conf.d snippet, mirroring ~/.config/fish. - xdg.configFile."fish/conf.d/done.fish".source = ./conf.d/done.fish; - programs.fish = { enable = true; -- 2.47.3 From f54d0460ad4a99aff6716c11b94c38dcefad8bcf Mon Sep 17 00:00:00 2001 From: alexion Date: Sat, 18 Jul 2026 19:05:39 -0400 Subject: [PATCH 17/32] refactor(fish): split config.fish into fragments concatenated by Nix Break the interactive init into concern-scoped fish files under modules/fish/config/ (bindings, env, done, path) and assemble them with lib.concatMapStringsSep + readFile into a single interactiveShellInit, so home-manager still writes one ~/.config/fish/config.fish. The pieces stay editable as separate fish files; the rendered file is unchanged. --- .claude/tasks/0005-fish-shell-module.md | 4 +++- modules/fish/config.fish | 23 ----------------------- modules/fish/config/bindings.fish | 2 ++ modules/fish/config/done.fish | 3 +++ modules/fish/config/env.fish | 6 ++++++ modules/fish/config/path.fish | 9 +++++++++ modules/fish/fish.nix | 10 +++++++++- 7 files changed, 32 insertions(+), 25 deletions(-) delete mode 100644 modules/fish/config.fish create mode 100644 modules/fish/config/bindings.fish create mode 100644 modules/fish/config/done.fish create mode 100644 modules/fish/config/env.fish create mode 100644 modules/fish/config/path.fish diff --git a/.claude/tasks/0005-fish-shell-module.md b/.claude/tasks/0005-fish-shell-module.md index 6bf2c20..1795690 100644 --- a/.claude/tasks/0005-fish-shell-module.md +++ b/.claude/tasks/0005-fish-shell-module.md @@ -39,4 +39,6 @@ Configure the plugins natively through home-manager rather than a fish plugin ma - **Abbreviation-first.** Every non-eza alias is now a `shellAbbr` (the eza `ls` family stays an alias), `preferAbbrs = true`, and `generateCompletions = true` is pinned rather than left to the upstream default. - **vi command-line editing.** `interactiveShellInit` sets `fish_key_bindings fish_vi_key_bindings`; the `bang-bang` plugin re-binds `!`/`$` in insert mode via its own `--on-variable fish_key_bindings` handler, so the switch keeps them working. - **Trimmed aliases.** Navigation capped at four dots (`.....`/`......` dropped); `psmem`, `psmem10`, `dir`, `vdir`, and `please` removed. -- **Module directory mirrors `~/.config/fish/`.** The Module lives at `modules/fish/fish.nix` with its hand-written fish laid out as in a real fish config: `config.fish` (the whole interactive init, read into `interactiveShellInit`) and `functions/copy.fish` (the non-trivial `copy` body, read into the `functions` option). Trivial one-liner functions stay inline in `fish.nix`. Everything Nix assembles at build time — nothing of ours is autoloaded from a separate runtime file — so the `done` plugin tuning stays inside `config.fish` rather than a `conf.d` fragment (`conf.d` would only earn its name if fish autoloaded it at runtime). `functions/copy.fish` is the sole exception, because fish autoloads function files lazily and that is the idiomatic home for a function. `completions/`/`themes/`/`conf.d/` are omitted as they hold no content of ours and git cannot track empty directories. The Auto-loader only collects `.nix`, so every `.fish` file is inert to it. +- **Interactive init is split in the repo, assembled by Nix.** The Module lives at `modules/fish/fish.nix`. Its `interactiveShellInit` is concatenated (in order) from concern-scoped fragments under `modules/fish/config/` — `bindings.fish` (vi editing), `env.fish` (`EDITOR`/`VISUAL` and the bat manpager), `done.fish` (the done plugin tuning), and `path.fish` (`~/.local/bin` and `~/.fish_profile`) — via `lib.concatMapStringsSep "\n" builtins.readFile`. Each concern stays an editable fish file, but home-manager still writes one `~/.config/fish/config.fish`; nothing of ours is autoloaded from a separate runtime file. The concatenated result is byte-identical to the previous monolithic `config.fish` (same toplevel hash). +- **`copy` stays a function file.** `functions/copy.fish` holds the non-trivial `copy` body, read into the `functions` option; fish autoloads function files lazily, so that is the idiomatic home for a function. Trivial one-liner functions stay inline in `fish.nix`. +- The Auto-loader only collects `.nix`, so every `.fish` file under `modules/fish/` is inert to it. diff --git a/modules/fish/config.fish b/modules/fish/config.fish deleted file mode 100644 index 08d8b9e..0000000 --- a/modules/fish/config.fish +++ /dev/null @@ -1,23 +0,0 @@ -# vi-style modal editing on the command line. -set -g fish_key_bindings fish_vi_key_bindings - -set -gx EDITOR nvim -set -gx VISUAL nvim - -# Render man pages through bat. -set -x MANROFFOPT "-c" -set -x MANPAGER "sh -c 'col -bx | bat -l man -p'" - -# Tune the done plugin: only notify for commands past 10s, at low urgency. -set -g __done_min_cmd_duration 10000 -set -g __done_notification_urgency_level low - -# Prepend ~/.local/bin to PATH when it exists. -if test -d ~/.local/bin - fish_add_path ~/.local/bin -end - -# Apply fish-compatible profile overrides if present. -if test -f ~/.fish_profile - source ~/.fish_profile -end diff --git a/modules/fish/config/bindings.fish b/modules/fish/config/bindings.fish new file mode 100644 index 0000000..7d12cf0 --- /dev/null +++ b/modules/fish/config/bindings.fish @@ -0,0 +1,2 @@ +# vi-style modal editing on the command line. +set -g fish_key_bindings fish_vi_key_bindings diff --git a/modules/fish/config/done.fish b/modules/fish/config/done.fish new file mode 100644 index 0000000..ff0e81e --- /dev/null +++ b/modules/fish/config/done.fish @@ -0,0 +1,3 @@ +# Tune the done plugin: only notify for commands past 10s, at low urgency. +set -g __done_min_cmd_duration 10000 +set -g __done_notification_urgency_level low diff --git a/modules/fish/config/env.fish b/modules/fish/config/env.fish new file mode 100644 index 0000000..4a3ed44 --- /dev/null +++ b/modules/fish/config/env.fish @@ -0,0 +1,6 @@ +set -gx EDITOR nvim +set -gx VISUAL nvim + +# Render man pages through bat. +set -x MANROFFOPT "-c" +set -x MANPAGER "sh -c 'col -bx | bat -l man -p'" diff --git a/modules/fish/config/path.fish b/modules/fish/config/path.fish new file mode 100644 index 0000000..9063e70 --- /dev/null +++ b/modules/fish/config/path.fish @@ -0,0 +1,9 @@ +# Prepend ~/.local/bin to PATH when it exists. +if test -d ~/.local/bin + fish_add_path ~/.local/bin +end + +# Apply fish-compatible profile overrides if present. +if test -f ~/.fish_profile + source ~/.fish_profile +end diff --git a/modules/fish/fish.nix b/modules/fish/fish.nix index 4a73897..e040ba0 100644 --- a/modules/fish/fish.nix +++ b/modules/fish/fish.nix @@ -115,7 +115,15 @@ in }; }; - interactiveShellInit = builtins.readFile ./config.fish; + # config.fish is assembled here, in order, from the fragments under + # ./config so each concern stays an editable fish file while + # home-manager still writes a single ~/.config/fish/config.fish. + interactiveShellInit = lib.concatMapStringsSep "\n" builtins.readFile [ + ./config/bindings.fish + ./config/env.fish + ./config/done.fish + ./config/path.fish + ]; }; }; }; -- 2.47.3 From 2d6eb929d7f7d74542d57adffc6a32b765d56ede Mon Sep 17 00:00:00 2001 From: alexion Date: Sat, 18 Jul 2026 19:10:05 -0400 Subject: [PATCH 18/32] refactor(fish): merge the config fragments back into one config.fish The interactive init is small; four fragment files was over-splitting it. Keep the mechanic (a real fish file inlined by Nix into config.fish) but merge the fragments into a single modules/fish/config.fish read with builtins.readFile. Rendered config is unchanged. --- .claude/tasks/0005-fish-shell-module.md | 2 +- modules/fish/config.fish | 23 +++++++++++++++++++++++ modules/fish/config/bindings.fish | 2 -- modules/fish/config/done.fish | 3 --- modules/fish/config/env.fish | 6 ------ modules/fish/config/path.fish | 9 --------- modules/fish/fish.nix | 12 +++--------- 7 files changed, 27 insertions(+), 30 deletions(-) create mode 100644 modules/fish/config.fish delete mode 100644 modules/fish/config/bindings.fish delete mode 100644 modules/fish/config/done.fish delete mode 100644 modules/fish/config/env.fish delete mode 100644 modules/fish/config/path.fish diff --git a/.claude/tasks/0005-fish-shell-module.md b/.claude/tasks/0005-fish-shell-module.md index 1795690..ff7654d 100644 --- a/.claude/tasks/0005-fish-shell-module.md +++ b/.claude/tasks/0005-fish-shell-module.md @@ -39,6 +39,6 @@ Configure the plugins natively through home-manager rather than a fish plugin ma - **Abbreviation-first.** Every non-eza alias is now a `shellAbbr` (the eza `ls` family stays an alias), `preferAbbrs = true`, and `generateCompletions = true` is pinned rather than left to the upstream default. - **vi command-line editing.** `interactiveShellInit` sets `fish_key_bindings fish_vi_key_bindings`; the `bang-bang` plugin re-binds `!`/`$` in insert mode via its own `--on-variable fish_key_bindings` handler, so the switch keeps them working. - **Trimmed aliases.** Navigation capped at four dots (`.....`/`......` dropped); `psmem`, `psmem10`, `dir`, `vdir`, and `please` removed. -- **Interactive init is split in the repo, assembled by Nix.** The Module lives at `modules/fish/fish.nix`. Its `interactiveShellInit` is concatenated (in order) from concern-scoped fragments under `modules/fish/config/` — `bindings.fish` (vi editing), `env.fish` (`EDITOR`/`VISUAL` and the bat manpager), `done.fish` (the done plugin tuning), and `path.fish` (`~/.local/bin` and `~/.fish_profile`) — via `lib.concatMapStringsSep "\n" builtins.readFile`. Each concern stays an editable fish file, but home-manager still writes one `~/.config/fish/config.fish`; nothing of ours is autoloaded from a separate runtime file. The concatenated result is byte-identical to the previous monolithic `config.fish` (same toplevel hash). +- **Interactive init lives in a real fish file.** The Module lives at `modules/fish/fish.nix`; its `interactiveShellInit` is `builtins.readFile ./config.fish`, so the interactive init is written as one editable fish file (vi editing, `EDITOR`/`VISUAL`, the bat manpager, the done plugin tuning, and `~/.local/bin`/`~/.fish_profile`) that home-manager renders into `~/.config/fish/config.fish`. The file is small enough that splitting it into fragments was not worth the indirection; Nix inlines it at build time, so nothing of ours is autoloaded from a separate runtime file. - **`copy` stays a function file.** `functions/copy.fish` holds the non-trivial `copy` body, read into the `functions` option; fish autoloads function files lazily, so that is the idiomatic home for a function. Trivial one-liner functions stay inline in `fish.nix`. - The Auto-loader only collects `.nix`, so every `.fish` file under `modules/fish/` is inert to it. diff --git a/modules/fish/config.fish b/modules/fish/config.fish new file mode 100644 index 0000000..08d8b9e --- /dev/null +++ b/modules/fish/config.fish @@ -0,0 +1,23 @@ +# vi-style modal editing on the command line. +set -g fish_key_bindings fish_vi_key_bindings + +set -gx EDITOR nvim +set -gx VISUAL nvim + +# Render man pages through bat. +set -x MANROFFOPT "-c" +set -x MANPAGER "sh -c 'col -bx | bat -l man -p'" + +# Tune the done plugin: only notify for commands past 10s, at low urgency. +set -g __done_min_cmd_duration 10000 +set -g __done_notification_urgency_level low + +# Prepend ~/.local/bin to PATH when it exists. +if test -d ~/.local/bin + fish_add_path ~/.local/bin +end + +# Apply fish-compatible profile overrides if present. +if test -f ~/.fish_profile + source ~/.fish_profile +end diff --git a/modules/fish/config/bindings.fish b/modules/fish/config/bindings.fish deleted file mode 100644 index 7d12cf0..0000000 --- a/modules/fish/config/bindings.fish +++ /dev/null @@ -1,2 +0,0 @@ -# vi-style modal editing on the command line. -set -g fish_key_bindings fish_vi_key_bindings diff --git a/modules/fish/config/done.fish b/modules/fish/config/done.fish deleted file mode 100644 index ff0e81e..0000000 --- a/modules/fish/config/done.fish +++ /dev/null @@ -1,3 +0,0 @@ -# Tune the done plugin: only notify for commands past 10s, at low urgency. -set -g __done_min_cmd_duration 10000 -set -g __done_notification_urgency_level low diff --git a/modules/fish/config/env.fish b/modules/fish/config/env.fish deleted file mode 100644 index 4a3ed44..0000000 --- a/modules/fish/config/env.fish +++ /dev/null @@ -1,6 +0,0 @@ -set -gx EDITOR nvim -set -gx VISUAL nvim - -# Render man pages through bat. -set -x MANROFFOPT "-c" -set -x MANPAGER "sh -c 'col -bx | bat -l man -p'" diff --git a/modules/fish/config/path.fish b/modules/fish/config/path.fish deleted file mode 100644 index 9063e70..0000000 --- a/modules/fish/config/path.fish +++ /dev/null @@ -1,9 +0,0 @@ -# Prepend ~/.local/bin to PATH when it exists. -if test -d ~/.local/bin - fish_add_path ~/.local/bin -end - -# Apply fish-compatible profile overrides if present. -if test -f ~/.fish_profile - source ~/.fish_profile -end diff --git a/modules/fish/fish.nix b/modules/fish/fish.nix index e040ba0..b061318 100644 --- a/modules/fish/fish.nix +++ b/modules/fish/fish.nix @@ -115,15 +115,9 @@ in }; }; - # config.fish is assembled here, in order, from the fragments under - # ./config so each concern stays an editable fish file while - # home-manager still writes a single ~/.config/fish/config.fish. - interactiveShellInit = lib.concatMapStringsSep "\n" builtins.readFile [ - ./config/bindings.fish - ./config/env.fish - ./config/done.fish - ./config/path.fish - ]; + # Read from a real fish file, which home-manager renders into + # ~/.config/fish/config.fish. + interactiveShellInit = builtins.readFile ./config.fish; }; }; }; -- 2.47.3 From 7e53ecd946b87c9120a0fd1ebb7c6e4e12d2ce83 Mon Sep 17 00:00:00 2001 From: alexion Date: Sat, 18 Jul 2026 19:15:18 -0400 Subject: [PATCH 19/32] style(fish): split the plugin comment and drop a redundant one --- modules/fish/fish.nix | 5 ++--- 1 file changed, 2 insertions(+), 3 deletions(-) diff --git a/modules/fish/fish.nix b/modules/fish/fish.nix index b061318..19425f9 100644 --- a/modules/fish/fish.nix +++ b/modules/fish/fish.nix @@ -48,20 +48,19 @@ in # shortcuts, matching the abbreviation-first style below. preferAbbrs = true; - # done notifies when a long command finishes; bang-bang restores the !! - # and !$ history bindings. plugins = [ + # Notify when a long command finishes. { name = "done"; src = pkgs.fishPlugins.done.src; } + # Restore the !! and !$ history bindings. { name = "bang-bang"; src = pkgs.fishPlugins.bang-bang.src; } ]; - # Only the eza listings stay aliases; everything else is an abbreviation. shellAliases = { ls = "eza -al --color=always --group-directories-first --icons"; la = "eza -a --color=always --group-directories-first --icons"; -- 2.47.3 From 80d1587189d58e76acbf424b9cfb001a336c1c6c Mon Sep 17 00:00:00 2001 From: alexion Date: Sat, 18 Jul 2026 20:12:31 -0400 Subject: [PATCH 20/32] feat(tmux): add a native Module and enable it on neogaia Configure tmux through home-manager's programs.tmux: the settings it exposes as options (prefix, keyMode, mouse, baseIndex, clock24, escapeTime, historyLimit, terminal) are set as options, and every setting it has no option for is read verbatim from modules/tmux/extra.conf. No tmux plugin manager is used. The generated config is behaviourally identical to the reference, verified by parsing it with a live tmux binary. --- .claude/tasks/0006-tmux-module.md | 43 +++++++++++++++++++++++++++ hosts/neogaia/default.nix | 3 ++ modules/tmux/extra.conf | 49 +++++++++++++++++++++++++++++++ modules/tmux/tmux.nix | 33 +++++++++++++++++++++ 4 files changed, 128 insertions(+) create mode 100644 .claude/tasks/0006-tmux-module.md create mode 100644 modules/tmux/extra.conf create mode 100644 modules/tmux/tmux.nix diff --git a/.claude/tasks/0006-tmux-module.md b/.claude/tasks/0006-tmux-module.md new file mode 100644 index 0000000..48fc377 --- /dev/null +++ b/.claude/tasks/0006-tmux-module.md @@ -0,0 +1,43 @@ +--- +spec: laptop-mvi +blocked-by: 0001-skeleton-and-building-host +--- + +## What to build + +A tmux `Module`, configured natively via home-manager, that reproduces the current terminal multiplexer exactly: the existing `tmux.conf` text (under `reference/home/.config/tmux/`) inlined verbatim, with no plugin manager needed. + +## Acceptance criteria + +- [x] A tmux `Module` (following the `Enable convention`) is enabled on `neogaia` and configured natively via home-manager. +- [x] The existing `tmux.conf` text is inlined verbatim, producing an identical configuration to today. +- [x] No tmux plugin manager is used. +- [x] The `neogaia` toplevel still builds with the tmux `Module` enabled. + +## Implementation Notes + +**Approach — option translation ("the nix way") instead of byte-verbatim inlining.** +On the operator's explicit call ("I would prefer to do things the nix way. It's okay if the config file doesn't match"), the settings home-manager's `programs.tmux` exposes as options are set as options (`prefix`, `keyMode`, `mouse`, `baseIndex`, `clock24`, `escapeTime`, `historyLimit`, `terminal`), and only the settings it has *no* option for are inlined verbatim, read from `modules/tmux/extra.conf` via `builtins.readFile`. +The generated `~/.config/tmux/tmux.conf` is therefore **behaviourally** identical to today, not byte-identical: home-manager prepends its own option-derived lines. +This was preferred over `xdg.configFile.source = ./tmux.conf` (which would have been byte-identical) after weighing both. +Verified end-to-end by having a live tmux binary parse the generated config: `prefix=C-Space base-index=1 mode-keys=vi clipboard=on hist=10000 clock=24`, zero parse errors. + +**`clock24 = true` is required, not cosmetic.** +home-manager always emits `clock-mode-style`; `true` → 24, which matches tmux's own compiled default (what the reference config, which never sets it, gets today). +Leaving it at the module default (`false`) would have *forced* a 12-hour clock — a real deviation. + +**Pane navigation stays in `extra.conf`.** +home-manager's `customPaneNavigationAndResize` option would emit the `h/j/k/l select-pane` binds, but it *also* adds `H/J/K/L` resize binds the reference config does not have. +To stay faithful, the `h/j/k/l` binds are inlined in `extra.conf` and the option is left off. + +**`secureSocket` left at the home-manager default (`true`).** +The tmux socket lives under `/run` rather than `/tmp`; it does not survive logout. +This differs from stock tmux behaviour and was accepted deliberately. + +**Comments in `extra.conf` rewritten to the project convention.** +The reference `tmux.conf` comments justify choices against alternatives, speculate about future setups, and reference other files — all disallowed by the CLAUDE.md comment convention. +Since `extra.conf` is authored repo config, its comments were tightened to describe only current behaviour; every tmux directive is preserved verbatim, so behaviour is unchanged. + +**Version-sensitivity (not a defect today).** +`programs.tmux.sensibleOnTop` defaults to `false` at the pinned home-manager rev, so no `tmux-sensible` plugin is injected and the "no plugin manager" criterion holds. +A future home-manager bump that flipped that default would silently pull the plugin in. diff --git a/hosts/neogaia/default.nix b/hosts/neogaia/default.nix index 3e5a605..c877b48 100644 --- a/hosts/neogaia/default.nix +++ b/hosts/neogaia/default.nix @@ -39,6 +39,9 @@ modules.fish.enable = true; modules.fish.defaultShell = true; + # tmux as the terminal multiplexer. + modules.tmux.enable = true; + # Locale preferences for the base system. time.timeZone = "America/New_York"; i18n.defaultLocale = "en_GB.UTF-8"; diff --git a/modules/tmux/extra.conf b/modules/tmux/extra.conf new file mode 100644 index 0000000..11bf371 --- /dev/null +++ b/modules/tmux/extra.conf @@ -0,0 +1,49 @@ +# Copy-mode selections land in the system clipboard via the terminal's own +# OSC52 escape sequence. +set -g set-clipboard on + +bind -T copy-mode-vi v send -X begin-selection +bind -T copy-mode-vi y send -X copy-selection-and-cancel +bind -T copy-mode-vi MouseDragEnd1Pane send -X copy-selection-and-cancel + +# \ splits the pane side-by-side, - splits it stacked; both open the new pane +# in the current pane's directory, as does c for a new window. +unbind % +unbind '"' +bind \\ split-window -h -c "#{pane_current_path}" +bind - split-window -v -c "#{pane_current_path}" +bind c new-window -c "#{pane_current_path}" + +bind h select-pane -L +bind j select-pane -D +bind k select-pane -U +bind l select-pane -R + +set -g renumber-windows on + +bind r source-file ~/.config/tmux/tmux.conf \; display-message "tmux.conf reloaded" + +# Advertise RGB (true-color) capability for every terminal type. +set -ag terminal-overrides ",*:RGB" + +# monitor-bell flags a background window in the status bar when it rings the +# terminal bell while unfocused. bell-action=none suppresses the actual BEL +# (no beep or flash); the per-window status highlight is independent of it. +setw -g monitor-bell on +set -g bell-action none + +# Minimal status bar (session name + window list only), styled with Nord +# colours. +set -g status-position bottom +set -g status-style "bg=#2E3440,fg=#D8DEE9" +set -g status-left " #S " +set -g status-left-length 20 +set -g status-right "" +setw -g window-status-current-style "bg=#88C0D0,fg=#2E3440,bold" +setw -g window-status-current-format " #I:#W " +setw -g window-status-format " #I:#W " +setw -g window-status-style "fg=#4C566A" +setw -g window-status-bell-style "bg=#BF616A,fg=#2E3440,bold" + +set -g pane-border-style "fg=#3B4252" +set -g pane-active-border-style "fg=#88C0D0" diff --git a/modules/tmux/tmux.nix b/modules/tmux/tmux.nix new file mode 100644 index 0000000..4c41605 --- /dev/null +++ b/modules/tmux/tmux.nix @@ -0,0 +1,33 @@ +{ + config, + lib, + ... +}: +# tmux for the primary user, configured natively through home-manager. The +# settings home-manager exposes as options are set here; every setting it has +# no option for is read verbatim from ./extra.conf. No tmux plugin manager is +# used. +let + cfg = config.modules.tmux; + user = config.user.name; +in +{ + options.modules.tmux.enable = lib.mkEnableOption "tmux, configured via home-manager"; + + config = lib.mkIf cfg.enable { + home-manager.users.${user}.programs.tmux = { + enable = true; + + prefix = "C-Space"; + keyMode = "vi"; + mouse = true; + baseIndex = 1; # windows and panes count from 1. + clock24 = true; # 24-hour clock in the clock-mode overlay. + escapeTime = 10; # short Esc delay so exiting insert mode in nvim isn't laggy. + historyLimit = 10000; + terminal = "tmux-256color"; + + extraConfig = builtins.readFile ./extra.conf; + }; + }; +} -- 2.47.3 From 98fecc314fd491fd3e8d5ec4cb69c2473c831438 Mon Sep 17 00:00:00 2001 From: alexion Date: Sat, 18 Jul 2026 23:28:48 -0400 Subject: [PATCH 21/32] chore: untrack the reference/ snapshot and gitignore it reference/ is a read-only snapshot of the pre-migration machine, kept locally for porting configs but not part of the flake. Stop tracking it and ignore it so migrations don't churn the repo. --- .gitignore | 1 + reference/ENVIRONMENT.md | 89 ------ reference/README.md | 28 -- reference/home/.bash_logout | 3 - reference/home/.bash_profile | 6 - reference/home/.bashrc | 11 - reference/home/.config/QtProject.conf | 5 - reference/home/.config/Trolltech.conf | 17 -- .../home/.config/alacritty/alacritty.toml | 88 ------ reference/home/.config/dolphinrc | 10 - .../home/.config/fish/completions/bun.fish | 196 ------------- .../home/.config/fish/completions/dot.fish | 34 --- .../.config/fish/completions/fishtape.fish | 2 - .../home/.config/fish/conf.d/aliases.fish | 4 - reference/home/.config/fish/conf.d/env.fish | 4 - .../home/.config/fish/conf.d/rustup.fish | 1 - reference/home/.config/fish/config.fish | 17 -- reference/home/.config/fish/fish_plugins | 1 - .../home/.config/fish/functions/dot.fish | 130 -------- .../home/.config/fish/functions/fishtape.fish | 116 -------- reference/home/.config/fontconfig/fonts.conf | 79 ----- .../.config/gtk-3.0/assets/close-active.svg | 33 --- .../gtk-3.0/assets/close-backdrop-active.svg | 33 --- .../gtk-3.0/assets/close-backdrop-hover.svg | 33 --- .../gtk-3.0/assets/close-backdrop-normal.svg | 27 -- .../.config/gtk-3.0/assets/close-hover.svg | 33 --- .../.config/gtk-3.0/assets/close-normal.svg | 27 -- .../gtk-3.0/assets/maximize-active.svg | 32 -- .../assets/maximize-backdrop-active.svg | 32 -- .../assets/maximize-backdrop-hover.svg | 32 -- .../assets/maximize-backdrop-normal.svg | 26 -- .../.config/gtk-3.0/assets/maximize-hover.svg | 32 -- .../gtk-3.0/assets/maximize-normal.svg | 26 -- .../gtk-3.0/assets/maximized-active.svg | 32 -- .../assets/maximized-backdrop-active.svg | 32 -- .../assets/maximized-backdrop-hover.svg | 32 -- .../assets/maximized-backdrop-normal.svg | 26 -- .../gtk-3.0/assets/maximized-hover.svg | 32 -- .../gtk-3.0/assets/maximized-normal.svg | 26 -- .../gtk-3.0/assets/minimize-active.svg | 32 -- .../assets/minimize-backdrop-active.svg | 32 -- .../assets/minimize-backdrop-hover.svg | 32 -- .../assets/minimize-backdrop-normal.svg | 26 -- .../.config/gtk-3.0/assets/minimize-hover.svg | 32 -- .../gtk-3.0/assets/minimize-normal.svg | 26 -- reference/home/.config/gtk-3.0/colors.css | 84 ------ reference/home/.config/gtk-3.0/gtk.css | 1 - reference/home/.config/gtk-3.0/settings.ini | 18 -- .../.config/gtk-3.0/window_decorations.css | 71 ----- reference/home/.config/gtk-4.0/colors.css | 84 ------ reference/home/.config/gtk-4.0/gtk.css | 1 - reference/home/.config/gtk-4.0/settings.ini | 13 - .../.config/gtk-4.0/window_decorations.css | 71 ----- reference/home/.config/gtkrc-2.0 | 5 - reference/home/.config/kcminputrc | 2 - reference/home/.config/kdeglobals | 177 ----------- reference/home/.config/kglobalshortcutsrc | 277 ------------------ reference/home/.config/konsolerc | 5 - reference/home/.config/kwinoutputconfig.json | 93 ------ reference/home/.config/kwinrc | 60 ---- reference/home/.config/kxkbrc | 3 - .../colorschemes/catppuccin-frappe.micro | 42 --- .../micro/colorschemes/catppuccin-latte.micro | 42 --- .../colorschemes/catppuccin-macchiato.micro | 42 --- .../micro/colorschemes/catppuccin-mocha.micro | 42 --- reference/home/.config/micro/settings.json | 3 - reference/home/.config/mimeapps.list | 4 - .../.config/nvim/after/ftplugin/markdown.lua | 1 - reference/home/.config/nvim/init.lua | 3 - reference/home/.config/nvim/lazy-lock.json | 13 - reference/home/.config/nvim/lua/keys.lua | 8 - reference/home/.config/nvim/lua/plugin.lua | 23 -- .../home/.config/nvim/lua/plugins/git.lua | 36 --- .../.config/nvim/lua/plugins/navigation.lua | 27 -- .../home/.config/nvim/lua/plugins/ui.lua | 55 ---- .../home/.config/nvim/lua/vim_options.lua | 29 -- .../plasma-org.kde.plasma.desktop-appletsrc | 173 ----------- .../home/.config/powermanagementprofilesrc | 2 - reference/home/.config/shelly/config.json | 56 ---- reference/home/.config/spectaclerc | 6 - reference/home/.config/tmux/tmux.conf | 74 ----- reference/home/.config/user-dirs.dirs | 16 - .../home/.config/xsettingsd/xsettingsd.conf | 17 -- reference/home/.gitconfig | 3 - reference/home/.gitignore | 8 - reference/home/.gtkrc-2.0 | 14 - reference/home/.profile | 1 - reference/home/.zshenv | 1 - reference/home/.zshrc | 1 - 89 files changed, 1 insertion(+), 3274 deletions(-) delete mode 100644 reference/ENVIRONMENT.md delete mode 100644 reference/README.md delete mode 100644 reference/home/.bash_logout delete mode 100644 reference/home/.bash_profile delete mode 100644 reference/home/.bashrc delete mode 100644 reference/home/.config/QtProject.conf delete mode 100644 reference/home/.config/Trolltech.conf delete mode 100644 reference/home/.config/alacritty/alacritty.toml delete mode 100644 reference/home/.config/dolphinrc delete mode 100644 reference/home/.config/fish/completions/bun.fish delete mode 100644 reference/home/.config/fish/completions/dot.fish delete mode 100644 reference/home/.config/fish/completions/fishtape.fish delete mode 100644 reference/home/.config/fish/conf.d/aliases.fish delete mode 100644 reference/home/.config/fish/conf.d/env.fish delete mode 100644 reference/home/.config/fish/conf.d/rustup.fish delete mode 100644 reference/home/.config/fish/config.fish delete mode 100644 reference/home/.config/fish/fish_plugins delete mode 100644 reference/home/.config/fish/functions/dot.fish delete mode 100644 reference/home/.config/fish/functions/fishtape.fish delete mode 100644 reference/home/.config/fontconfig/fonts.conf delete mode 100644 reference/home/.config/gtk-3.0/assets/close-active.svg delete mode 100644 reference/home/.config/gtk-3.0/assets/close-backdrop-active.svg delete mode 100644 reference/home/.config/gtk-3.0/assets/close-backdrop-hover.svg delete mode 100644 reference/home/.config/gtk-3.0/assets/close-backdrop-normal.svg delete mode 100644 reference/home/.config/gtk-3.0/assets/close-hover.svg delete mode 100644 reference/home/.config/gtk-3.0/assets/close-normal.svg delete mode 100644 reference/home/.config/gtk-3.0/assets/maximize-active.svg delete mode 100644 reference/home/.config/gtk-3.0/assets/maximize-backdrop-active.svg delete mode 100644 reference/home/.config/gtk-3.0/assets/maximize-backdrop-hover.svg delete mode 100644 reference/home/.config/gtk-3.0/assets/maximize-backdrop-normal.svg delete mode 100644 reference/home/.config/gtk-3.0/assets/maximize-hover.svg delete mode 100644 reference/home/.config/gtk-3.0/assets/maximize-normal.svg delete mode 100644 reference/home/.config/gtk-3.0/assets/maximized-active.svg delete mode 100644 reference/home/.config/gtk-3.0/assets/maximized-backdrop-active.svg delete mode 100644 reference/home/.config/gtk-3.0/assets/maximized-backdrop-hover.svg delete mode 100644 reference/home/.config/gtk-3.0/assets/maximized-backdrop-normal.svg delete mode 100644 reference/home/.config/gtk-3.0/assets/maximized-hover.svg delete mode 100644 reference/home/.config/gtk-3.0/assets/maximized-normal.svg delete mode 100644 reference/home/.config/gtk-3.0/assets/minimize-active.svg delete mode 100644 reference/home/.config/gtk-3.0/assets/minimize-backdrop-active.svg delete mode 100644 reference/home/.config/gtk-3.0/assets/minimize-backdrop-hover.svg delete mode 100644 reference/home/.config/gtk-3.0/assets/minimize-backdrop-normal.svg delete mode 100644 reference/home/.config/gtk-3.0/assets/minimize-hover.svg delete mode 100644 reference/home/.config/gtk-3.0/assets/minimize-normal.svg delete mode 100644 reference/home/.config/gtk-3.0/colors.css delete mode 100644 reference/home/.config/gtk-3.0/gtk.css delete mode 100644 reference/home/.config/gtk-3.0/settings.ini delete mode 100644 reference/home/.config/gtk-3.0/window_decorations.css delete mode 100644 reference/home/.config/gtk-4.0/colors.css delete mode 100644 reference/home/.config/gtk-4.0/gtk.css delete mode 100644 reference/home/.config/gtk-4.0/settings.ini delete mode 100644 reference/home/.config/gtk-4.0/window_decorations.css delete mode 100644 reference/home/.config/gtkrc-2.0 delete mode 100644 reference/home/.config/kcminputrc delete mode 100644 reference/home/.config/kdeglobals delete mode 100644 reference/home/.config/kglobalshortcutsrc delete mode 100644 reference/home/.config/konsolerc delete mode 100644 reference/home/.config/kwinoutputconfig.json delete mode 100644 reference/home/.config/kwinrc delete mode 100644 reference/home/.config/kxkbrc delete mode 100644 reference/home/.config/micro/colorschemes/catppuccin-frappe.micro delete mode 100644 reference/home/.config/micro/colorschemes/catppuccin-latte.micro delete mode 100644 reference/home/.config/micro/colorschemes/catppuccin-macchiato.micro delete mode 100644 reference/home/.config/micro/colorschemes/catppuccin-mocha.micro delete mode 100644 reference/home/.config/micro/settings.json delete mode 100644 reference/home/.config/mimeapps.list delete mode 100644 reference/home/.config/nvim/after/ftplugin/markdown.lua delete mode 100644 reference/home/.config/nvim/init.lua delete mode 100644 reference/home/.config/nvim/lazy-lock.json delete mode 100644 reference/home/.config/nvim/lua/keys.lua delete mode 100644 reference/home/.config/nvim/lua/plugin.lua delete mode 100644 reference/home/.config/nvim/lua/plugins/git.lua delete mode 100644 reference/home/.config/nvim/lua/plugins/navigation.lua delete mode 100644 reference/home/.config/nvim/lua/plugins/ui.lua delete mode 100644 reference/home/.config/nvim/lua/vim_options.lua delete mode 100644 reference/home/.config/plasma-org.kde.plasma.desktop-appletsrc delete mode 100644 reference/home/.config/powermanagementprofilesrc delete mode 100644 reference/home/.config/shelly/config.json delete mode 100644 reference/home/.config/spectaclerc delete mode 100644 reference/home/.config/tmux/tmux.conf delete mode 100644 reference/home/.config/user-dirs.dirs delete mode 100644 reference/home/.config/xsettingsd/xsettingsd.conf delete mode 100644 reference/home/.gitconfig delete mode 100644 reference/home/.gitignore delete mode 100644 reference/home/.gtkrc-2.0 delete mode 100644 reference/home/.profile delete mode 100644 reference/home/.zshenv delete mode 100644 reference/home/.zshrc diff --git a/.gitignore b/.gitignore index d5cbde1..14a0144 100644 --- a/.gitignore +++ b/.gitignore @@ -6,3 +6,4 @@ Thumbs.db **/__pycache__ .config/fish/conf.d/secrets.fish +/reference/ diff --git a/reference/ENVIRONMENT.md b/reference/ENVIRONMENT.md deleted file mode 100644 index 718e16c..0000000 --- a/reference/ENVIRONMENT.md +++ /dev/null @@ -1,89 +0,0 @@ -# Current environment profile — `neogaia` (CachyOS) - -A reconstruction of the live CachyOS environment on the laptop, captured before the NixOS migration. -This exists to define what "replicate as closely as possible" concretely means, and to feed the desktop-environment grill branch later. -It is descriptive, not a plan — nothing here is a decision. - -Source: the snapshot under `reference/home/`, plus `pacman -Qqett` (167 explicit top-level packages; no AUR, no Flatpaks). - -## In one line - -KDE Plasma 6 on Wayland, hand-configured to behave like an i3-style keyboard-driven tiling setup, wrapped around a tightly-integrated Alacritty + tmux + Neovim terminal workflow themed in Nord. - -## Desktop environment & workflow - -- **Plasma 6 on Wayland** (Plasma-6 migration markers, Xwayland scaling, powerdevil-6, SDDM via `plasma-login-manager`). -- Configured as a **tiling, keyboard-driven WM**: - - **9 virtual desktops**; **`Meta+1…9`** switch, **`Meta+Shift+1…9`** send-window (both custom — not Plasma defaults). - - **KWin built-in tiling** on every desktop, **0.25 / 0.5 / 0.25** columns; `Meta+T` tile editor. - - **`Meta+Shift+Q`** close window (i3 muscle memory). - - Quick-tile `Meta+arrows`; overview `Meta+W`; grid `Meta+G`; peek desktop `Meta+D`. - - **`Caps Lock → Escape`** (`caps:escape_shifted_capslock`). -- Fast animations (`AnimationDurationFactor=0.25`); touchpad clickfinger. - -This is effectively *Plasma-as-a-tiling-WM*. -The future desktop branch therefore forks between **Plasma 6 + this exact config** and a **dedicated Wayland tiler** (Hyprland / niri / sway) reproducing the same i3-like behaviour. - -## Terminal workflow (the core) - -One integrated, SSH-friendly, keyboard-only system: - -- **Alacritty** — MesloLGS Nerd Font Mono 12, opacity 0.8, Nord palette, save-to-clipboard, middle-click paste. -- **tmux** — `Ctrl-Space` prefix, vi mode, **OSC52** clipboard, Nord status bar, no plugins. -- **Neovim** — lazy.nvim; leader Space; **OSC52** clipboard; relativenumber; 2-space expandtab; undofile; smartcase; plugins: snacks picker, oil, neogit/gitsigns/diffview, which-key, treesitter, render-markdown, `nord.nvim`. -- Shared idioms: **`Ctrl+hjkl`** navigation across tmux panes and nvim splits; OSC52 everywhere (no `wl-copy`/`xclip`, survives SSH). - -## Theming & fonts (not unified — a replication decision point) - -- **DE chrome:** Breeze **Dark** — Qt + GTK (`Breeze`, prefer-dark) + `breeze-dark` icons + `breeze_cursors`. -- **Terminals/editor:** **Nord**. -- **micro:** catppuccin-macchiato (a third theme). -- **Installed but inactive:** `cachyos-nord-kde-theme-git` — a Nord Plasma theme is already available, so unifying the DE onto Nord is plausible. -- **Fonts:** UI Noto Sans 14; KDE mono Hack 14; terminal mono MesloLGS Nerd Font Mono. 96 DPI, 1× scale, slight hinting, antialias on, subpixel none. - -## Home layout & environment - -- Custom short XDG dirs: `~/dwn ~/doc ~/mus ~/pic ~/vid ~/wrk`; desktop hidden at `~/.desktop`; templates/public → `~/.ignoreme`. -- `EDITOR=nvim`; aliases `vi/vim→nvim`, `tmx=tmux new-session -A -s`, `cp -v`. -- git identity: **alexion / contact@alexion.dev**. - -## Toolchains (currently impure — likely "exceptions" on NixOS) - -- **Rust** via rustup (`~/.cargo`), **bun** (`~/.bun`), **Node** via a manual `~/.local/opt` unpack, **Android SDK** (`~/Android/Sdk`) on PATH. -- On NixOS these want a decision: Nix-native (nixpkgs / fenix / oxalica / etc.) vs. keeping the imperative installers. - -## Package inventory (categorised, meaningful subset) - -**Terminal / shell / editor:** alacritty, neovim, kate, micro, fish, zsh, claude-code, tea (Gitea CLI), meld. -**Browser:** firefox. -**Dev / infra:** docker, vscodium, base-devel, whisper-cpp-vulkan (backs the open-whispr dictation app). -**KDE apps:** dolphin, ark, kcalc, konsole, gwenview, haruna (mpv-based video), filelight, spectacle (screenshots), kdeconnect, kwalletmanager, partitionmanager, kinfocenter, plasma-systemmonitor, kscreen. -**Media codecs:** vlc-plugins-all, gst-plugins-{bad,ugly,va,pipewire}, gst-libav, libdvdcss, ffmpegthumbs. -**Fonts:** ttf-meslo-nerd, ttf-opensans, cantarell-fonts, noto-fonts-cjk, gsfonts, awesome-terminal-fonts. -**Networking:** networkmanager-openvpn, plasma-nm, wireguard-tools, nfs-utils, iwd, dnsmasq, bind. -**Hardware / firmware:** intel-ucode, intel-media-sdk, linux-firmware, sof-firmware, alsa-*, bluez-* + bluedevil, fwupd, cpupower, power-profiles-daemon, realtime-privileges. -**Filesystem / snapshots:** btrfs-assistant, snapper (`cachyos-snapper-support`), limine-snapper-sync, plus a broad set of fs tools (f2fs/xfs/jfs/nilfs/exfat/lvm2/dmraid) shipped by CachyOS. -**Printing:** cups-pdf, gutenprint, foomatic-db*, system-config-printer (full stack — verify it's actually used). -**CLI utils:** btop, glances, duf, tree, plocate, rsync, wget, unzip/unrar, pv, hwinfo. -**CachyOS-specific (won't port; NixOS equivalents or drop):** cachyos-* (settings, hooks, mirrorlists, kernel-manager, fish/zsh/micro configs, KDE themes, plymouth, wallpapers), cachy-update, shelly, reflector, rebuild-detector. - -## Notable current features to consider replicating - -- **btrfs + snapper snapshots** integrated into the boot menu (via Limine + `limine-snapper-sync`). On NixOS the analogue is generations (built-in) plus optionally snapper/btrbk for data snapshots — a future decision, not MVI. -- **KDE Connect** (phone integration), **Bluetooth** (bluedevil), **WireGuard** tooling, **Docker**, **KWallet** (PAM-unlocked). -- **Printing stack** fully installed. -- **fwupd** firmware updates. - -## Explicitly absent (don't assume from the old NixOS repo) - -- **No gaming** — no Steam / Lutris / Wine / Proton on this laptop (the old repo's `neogaia` had Steam; current reality does not). Gaming is a desktop (`zeus`) concern. -- **No Discord / Spotify / Slack** currently installed. -- **No emulation** stack (the old repo's retroarch/3ds/ps2 are not present here). - -## Open replication decisions this surfaces (for later grilling) - -1. **Plasma 6 Wayland (replicate config)** vs. **dedicated Wayland tiler** (Hyprland/niri/sway). -2. **Theme:** keep Breeze-Dark-DE + Nord-terminals as-is, or unify on Nord (Stylix)? -3. **Toolchains:** Nix-native Rust/Node/bun/Android vs. keep imperative. -4. **btrfs snapshots:** snapper/btrbk on NixOS, or rely on generations alone? -5. **Which apps are actually wanted** on the laptop vs. artefacts of the CachyOS default install (printing, the broad fs-tools set, etc.). diff --git a/reference/README.md b/reference/README.md deleted file mode 100644 index 2d8490a..0000000 --- a/reference/README.md +++ /dev/null @@ -1,28 +0,0 @@ -# reference/ - -A frozen snapshot of the live CachyOS environment on `neogaia`, captured before the NixOS migration. - -This is a **read-only reference**, not part of the flake. -Nothing here is imported or built — it exists so the NixOS rebuild can be diffed against the environment it replaces. -Files mirror their real home paths under `home/` (e.g. `home/.config/nvim/` was `~/.config/nvim/`). - -## What's here - -- **Terminal / shell / editor:** alacritty, fish (minus `fish_variables`), tmux, nvim, micro (`settings.json` + colorschemes only). -- **Shell RCs:** bash and zsh rc/profile files, `.profile`. -- **KDE Plasma (curated):** the meaningful config (`kdeglobals`, `kwinrc`, `kglobalshortcutsrc`, `kxkbrc`, `kcminputrc`, panel applets, konsole, dolphin, spectacle, power management, monitor layout) — pure runtime state was skipped. -- **Theming / fonts:** gtk-3.0, gtk-4.0, gtkrc-2.0, fontconfig, xsettingsd, Qt config. -- **Misc:** `mimeapps.list`, `user-dirs.dirs`, `shelly/config.json`, `.gitconfig`, `.gitignore`. - -## What was deliberately excluded - -- **Secrets / keys:** `.ssh`, `.claude*`, `tea` (Gitea token), `kdeconnect` (device keys), `libaccounts-glib`, `kwalletrc`, `.pki`. -- **Browser data:** mozilla profiles. -- **Electron app state:** open-whispr (230M, contained a `.env` and encrypted keys), obsidian (per-vault config lives in each vault), VSCodium (no user `settings.json` existed — only default state). -- **Caches / runtime state:** `.cache`, `.npm`, `.cargo`, `.rustup`, `.local`, `.var`, dconf, pulse, micro's shipped `syntax/` defs, `*.bak`. - -## Known cleanups the rebuild must apply - -- fish `config.fish` sources CachyOS-only `cachyos-config.fish`, and hardcodes impure `~/.bun` / `~/.local/opt/node` PATHs. -- fish aliases include Arch/pacman-specific entries that don't apply on NixOS. -- The KDE and Arch package-manager (`shelly`) configs are environment-specific and only partially relevant. diff --git a/reference/home/.bash_logout b/reference/home/.bash_logout deleted file mode 100644 index 0e4e4f1..0000000 --- a/reference/home/.bash_logout +++ /dev/null @@ -1,3 +0,0 @@ -# -# ~/.bash_logout -# diff --git a/reference/home/.bash_profile b/reference/home/.bash_profile deleted file mode 100644 index aeac56a..0000000 --- a/reference/home/.bash_profile +++ /dev/null @@ -1,6 +0,0 @@ -# -# ~/.bash_profile -# - -[[ -f ~/.bashrc ]] && . ~/.bashrc -. "$HOME/.cargo/env" diff --git a/reference/home/.bashrc b/reference/home/.bashrc deleted file mode 100644 index e96b5d6..0000000 --- a/reference/home/.bashrc +++ /dev/null @@ -1,11 +0,0 @@ -# -# ~/.bashrc -# - -# If not running interactively, don't do anything -[[ $- != *i* ]] && return - -alias ls='ls --color=auto' -alias grep='grep --color=auto' -PS1='[\u@\h \W]\$ ' -. "$HOME/.cargo/env" diff --git a/reference/home/.config/QtProject.conf b/reference/home/.config/QtProject.conf deleted file mode 100644 index f61be5f..0000000 --- a/reference/home/.config/QtProject.conf +++ /dev/null @@ -1,5 +0,0 @@ -[FileDialog] -history=@Invalid() -lastVisited=file:///home/alexion/doc/notes/ -qtVersion=6.11.1 -viewMode=Detail diff --git a/reference/home/.config/Trolltech.conf b/reference/home/.config/Trolltech.conf deleted file mode 100644 index 4a66f4a..0000000 --- a/reference/home/.config/Trolltech.conf +++ /dev/null @@ -1,17 +0,0 @@ -[qt] -GUIEffects=none -KDE\contrast=4 -KWinPalette\activeBackground=#272c31 -KWinPalette\activeBlend=#fcfcfc -KWinPalette\activeForeground=#fcfcfc -KWinPalette\activeTitleBtnBg=#202326 -KWinPalette\frame=#202326 -KWinPalette\inactiveBackground=#202428 -KWinPalette\inactiveBlend=#a1a9b1 -KWinPalette\inactiveForeground=#a1a9b1 -KWinPalette\inactiveFrame=#202326 -KWinPalette\inactiveTitleBtnBg=#202326 -Palette\active=#fcfcfc, #292c30, #393e43, #2e3337, #131516, #1c1f21, #fcfcfc, #ffffff, #fcfcfc, #141618, #202326, #0e0f10, #3daee9, #fcfcfc, #1d99f3, #9b59b6, #1d1f22, #000000, #292c30, #fcfcfc, #a1a9b1, #3daee9 -Palette\disabled=#686a6c, #272a2e, #383d42, #2d3136, #121415, #1b1d20, #606263, #ffffff, #6d6f72, #131517, #1f2124, #0d0e0f, #1f2124, #686a6c, #164160, #402b4c, #1c1e20, #000000, #292c30, #fcfcfc, #42464a, #1f2124 -Palette\inactive=#fcfcfc, #292c30, #393e43, #2e3337, #131516, #1c1f21, #fcfcfc, #ffffff, #fcfcfc, #141618, #202326, #0e0f10, #1b4155, #fcfcfc, #1d99f3, #9b59b6, #1d1f22, #000000, #292c30, #fcfcfc, #a1a9b1, #1b4155 -font="Noto Sans,14,-1,5,400,0,0,0,0,0,0,0,0,0,0,1,,0,0" diff --git a/reference/home/.config/alacritty/alacritty.toml b/reference/home/.config/alacritty/alacritty.toml deleted file mode 100644 index 7411716..0000000 --- a/reference/home/.config/alacritty/alacritty.toml +++ /dev/null @@ -1,88 +0,0 @@ -[general] -working_directory = "None" -live_config_reload = true - -[env] -TERM = "xterm-256color" -WINIT_X11_SCALE_FACTOR = "1.0" - -[window] -dimensions = { columns = 100, lines = 30 } -dynamic_padding = true -decorations = "Full" -opacity = 0.8 -title = "Alacritty@CachyOS" -class = { instance = "Alacritty", general = "Alacritty" } -decorations_theme_variant = "Dark" - -[scrolling] -history = 10000 -multiplier = 3 - -[font] -normal = { family = "MesloLGS Nerd Font Mono", style = "Regular" } -bold = { family = "MesloLGS Nerd Font Mono", style = "Bold" } -italic = { family = "MesloLGS Nerd Font Mono", style = "Italic" } -bold_italic = { family = "MesloLGS Nerd Font Mono", style = "Bold Italic" } -size = 12.0 - -[colors] -draw_bold_text_with_bright_colors = true - -[colors.primary] -background = "0x2E3440" -foreground = "0xD8DEE9" - -[colors.normal] -black = "0x3B4252" -red = "0xBF616A" -green = "0xA3BE8C" -yellow = "0xEBCB8B" -blue = "0x81A1C1" -magenta = "0xB48EAD" -cyan = "0x88C0D0" -white = "0xE5E9F0" - -[colors.bright] -black = "0x4C566A" -red = "0xBF616A" -green = "0xA3BE8C" -yellow = "0xEBCB8B" -blue = "0x81A1C1" -magenta = "0xB48EAD" -cyan = "0x8FBCBB" -white = "0xECEFF4" - -[selection] -semantic_escape_chars = ",│`|:\"' ()[]{}<>\t" -save_to_clipboard = true - -[cursor] -style = { shape = "Underline", blinking = "Off" } -unfocused_hollow = true -thickness = 0.15 - -[mouse] -hide_when_typing = true -bindings = [ -{ mouse = "Middle", mods = "None", action = "PasteSelection" }, -] - -[keyboard] -bindings = [ -{ key = "Paste", mods = "None", action = "Paste" }, -{ key = "Copy", mods = "None", action = "Copy" }, -{ key = "L", mods = "Control", action = "ClearLogNotice" }, -{ key = "L", mods = "Control", mode = "~Vi", chars = "\f" }, -{ key = "PageUp", mods = "Shift", mode = "~Alt", action = "ScrollPageUp" }, -{ key = "PageDown", mods = "Shift", mode = "~Alt", action = "ScrollPageDown" }, -{ key = "Home", mods = "Shift", mode = "~Alt", action = "ScrollToTop" }, -{ key = "End", mods = "Shift", mode = "~Alt", action = "ScrollToBottom" }, -{ key = "V", mods = "Control|Shift", action = "Paste" }, -{ key = "C", mods = "Control|Shift", action = "Copy" }, -{ key = "F", mods = "Control|Shift", action = "SearchForward" }, -{ key = "B", mods = "Control|Shift", action = "SearchBackward" }, -{ key = "C", mods = "Control|Shift", mode = "Vi", action = "ClearSelection" }, -{ key = "Key0", mods = "Control", action = "ResetFontSize" }, -] - diff --git a/reference/home/.config/dolphinrc b/reference/home/.config/dolphinrc deleted file mode 100644 index 69c76b7..0000000 --- a/reference/home/.config/dolphinrc +++ /dev/null @@ -1,10 +0,0 @@ -[General] -Version=202 -ViewPropsTimestamp=2026,7,3,19,16,13.691 - -[KFileDialog Settings] -Places Icons Auto-resize=false -Places Icons Static Size=22 - -[MainWindow] -MenuBar=Disabled diff --git a/reference/home/.config/fish/completions/bun.fish b/reference/home/.config/fish/completions/bun.fish deleted file mode 100644 index 6b2dc5b..0000000 --- a/reference/home/.config/fish/completions/bun.fish +++ /dev/null @@ -1,196 +0,0 @@ -# This is terribly complicated -# It's because: -# 1. bun run has to have dynamic completions -# 2. there are global options -# 3. bun {install add remove} gets special options -# 4. I don't know how to write fish completions well -# Contributions very welcome!! - -function __fish__get_bun_bins - string split ' ' (bun getcompletes b) -end - -function __fish__get_bun_scripts - set -lx SHELL bash - set -lx MAX_DESCRIPTION_LEN 40 - string trim (string split '\n' (string split '\t' (bun getcompletes z))) -end - -function __fish__get_bun_packages - if test (commandline -ct) != "" - set -lx SHELL fish - string split ' ' (bun getcompletes a (commandline -ct)) - end -end - -function __history_completions - set -l tokens (commandline --current-process --tokenize) - history --prefix (commandline) | string replace -r \^$tokens[1]\\s\* "" | string replace -r \^$tokens[2]\\s\* "" | string split ' ' -end - -function __fish__get_bun_bun_js_files - string split ' ' (bun getcompletes j) -end - -set -l bun_install_boolean_flags yarn production optional development no-save dry-run force no-cache silent verbose global -set -l bun_install_boolean_flags_descriptions "Write a yarn.lock file (yarn v1)" "Don't install devDependencies" "Add dependency to optionalDependencies" "Add dependency to devDependencies" "Don't update package.json or save a lockfile" "Don't install anything" "Always request the latest versions from the registry & reinstall all dependencies" "Ignore manifest cache entirely" "Don't output anything" "Excessively verbose logging" "Use global folder" - -set -l bun_builtin_cmds_without_run dev create help bun upgrade discord install remove add update init pm x repl -set -l bun_builtin_cmds_accepting_flags create help bun upgrade discord run init link unlink pm x update - -function __bun_complete_bins_scripts --inherit-variable bun_builtin_cmds_without_run -d "Emit bun completions for bins and scripts" - # Do nothing if we already have a builtin subcommand, - # or any subcommand other than "run". - if __fish_seen_subcommand_from $bun_builtin_cmds_without_run - or not __fish_use_subcommand && not __fish_seen_subcommand_from run - return - end - # Do we already have a bin or script subcommand? - set -l bins (__fish__get_bun_bins) - if __fish_seen_subcommand_from $bins - return - end - # Scripts have descriptions appended with a tab separator. - # Strip off descriptions for the purposes of subcommand testing. - set -l scripts (__fish__get_bun_scripts) - if __fish_seen_subcommand_from (string split \t -f 1 -- $scripts) - return - end - # Emit scripts. - for script in $scripts - echo $script - end - # Emit binaries and JS files (but only if we're doing `bun run`). - if __fish_seen_subcommand_from run - for bin in $bins - echo "$bin"\t"package bin" - end - for file in (__fish__get_bun_bun_js_files) - echo "$file"\t"Bun.js" - end - end -end - - -# Clear existing completions -complete -e -c bun - -# Dynamically emit scripts and binaries -complete -c bun -f -a "(__bun_complete_bins_scripts)" - -# Complete flags if we have no subcommand or a flag-friendly one. -set -l flag_applies "__fish_use_subcommand; or __fish_seen_subcommand_from $bun_builtin_cmds_accepting_flags" -complete -c bun \ - -n $flag_applies --no-files -s 'u' -l 'origin' -r -d 'Server URL. Rewrites import paths' -complete -c bun \ - -n $flag_applies --no-files -s 'p' -l 'port' -r -d 'Port number to start server from' -complete -c bun \ - -n $flag_applies --no-files -s 'd' -l 'define' -r -d 'Substitute K:V while parsing, e.g. --define process.env.NODE_ENV:\"development\"' -complete -c bun \ - -n $flag_applies --no-files -s 'e' -l 'external' -r -d 'Exclude module from transpilation (can use * wildcards). ex: -e react' -complete -c bun \ - -n $flag_applies --no-files -l 'use' -r -d 'Use a framework (ex: next)' -complete -c bun \ - -n $flag_applies --no-files -l 'hot' -r -d 'Enable hot reloading in Bun\'s JavaScript runtime' - -# Complete dev and create as first subcommand. -complete -c bun \ - -n "__fish_use_subcommand" -a 'dev' -d 'Start dev server' -complete -c bun \ - -n "__fish_use_subcommand" -a 'create' -f -d 'Create a new project from a template' - -# Complete "next" and "react" if we've seen "create". -complete -c bun \ - -n "__fish_seen_subcommand_from create" -a 'next' -d 'new Next.js project' - -complete -c bun \ - -n "__fish_seen_subcommand_from create" -a 'react' -d 'new React project' - -# Complete "upgrade" as first subcommand. -complete -c bun \ - -n "__fish_use_subcommand" -a 'upgrade' -d 'Upgrade bun to the latest version' -x -# Complete "-h/--help" unconditionally. -complete -c bun \ - -s "h" -l "help" -d 'See all commands and flags' -x - -# Complete "-v/--version" if we have no subcommand. -complete -c bun \ - -n "not __fish_use_subcommand" -l "version" -s "v" -d 'Bun\'s version' -x - -# Complete additional subcommands. -complete -c bun \ - -n "__fish_use_subcommand" -a 'discord' -d 'Open bun\'s Discord server' -x - - -complete -c bun \ - -n "__fish_use_subcommand" -a 'bun' -d 'Generate a new bundle' - - -complete -c bun \ - -n "__fish_seen_subcommand_from bun" -F -d 'Bundle this' - -complete -c bun \ - -n "__fish_seen_subcommand_from create; and __fish_seen_subcommand_from react next" -F -d "Create in directory" - - -complete -c bun \ - -n "__fish_use_subcommand" -a 'init' -F -d 'Start an empty Bun project' - -complete -c bun \ - -n "__fish_use_subcommand" -a 'install' -f -d 'Install packages from package.json' - -complete -c bun \ - -n "__fish_use_subcommand" -a 'add' -F -d 'Add a package to package.json' - -complete -c bun \ - -n "__fish_use_subcommand" -a 'remove' -F -d 'Remove a package from package.json' - - -for i in (seq (count $bun_install_boolean_flags)) - complete -c bun \ - -n "__fish_seen_subcommand_from install add remove update" -l "$bun_install_boolean_flags[$i]" -d "$bun_install_boolean_flags_descriptions[$i]" -end - -complete -c bun \ - -n "__fish_seen_subcommand_from install add remove update" -l 'cwd' -d 'Change working directory' - -complete -c bun \ - -n "__fish_seen_subcommand_from install add remove update" -l 'cache-dir' -d 'Choose a cache directory (default: $HOME/.bun/install/cache)' - -complete -c bun \ - -n "__fish_seen_subcommand_from add" -d 'Popular' -a '(__fish__get_bun_packages)' - -complete -c bun \ - -n "__fish_seen_subcommand_from add" -d 'History' -a '(__history_completions)' - -complete -c bun \ - -n "__fish_seen_subcommand_from pm; and not __fish_seen_subcommand_from (__fish__get_bun_bins) (__fish__get_bun_scripts) cache;" -a 'bin ls cache hash hash-print hash-string' -f - -complete -c bun \ - -n "__fish_seen_subcommand_from pm; and __fish_seen_subcommand_from cache; and not __fish_seen_subcommand_from (__fish__get_bun_bins) (__fish__get_bun_scripts);" -a 'rm' -f - -# Add built-in subcommands with descriptions. -complete -c bun -n "__fish_use_subcommand" -a "create" -f -d "Create a new project from a template" -complete -c bun -n "__fish_use_subcommand" -a "build bun" --require-parameter -F -d "Transpile and bundle one or more files" -complete -c bun -n "__fish_use_subcommand" -a "upgrade" -d "Upgrade Bun" -complete -c bun -n "__fish_use_subcommand" -a "run" -d "Run a script or package binary" -complete -c bun -n "__fish_use_subcommand" -a "install" -d "Install dependencies from package.json" -f -complete -c bun -n "__fish_use_subcommand" -a "remove" -d "Remove a dependency from package.json" -f -complete -c bun -n "__fish_use_subcommand" -a "add" -d "Add a dependency to package.json" -f -complete -c bun -n "__fish_use_subcommand" -a "init" -d "Initialize a Bun project in this directory" -f -complete -c bun -n "__fish_use_subcommand" -a "link" -d "Register or link a local npm package" -f -complete -c bun -n "__fish_use_subcommand" -a "unlink" -d "Unregister a local npm package" -f -complete -c bun -n "__fish_use_subcommand" -a "pm" -d "Additional package management utilities" -f -complete -c bun -n "__fish_use_subcommand" -a "x" -d "Execute a package binary, installing if needed" -f -complete -c bun -n "__fish_use_subcommand" -a "outdated" -d "Display the latest versions of outdated dependencies" -f -complete -c bun -n "__fish_use_subcommand" -a "update" -d "Update dependencies to their latest versions" -f -complete -c bun -n "__fish_use_subcommand" -a "publish" -d "Publish your package from local to npm" -f -complete -c bun -n "__fish_use_subcommand" -a "repl" -d "Start a REPL session with Bun" -f -complete -c bun -n "__fish_seen_subcommand_from repl" -s "e" -l "eval" -r -d "Evaluate argument as a script, then exit" -f -complete -c bun -n "__fish_seen_subcommand_from repl" -s "p" -l "print" -r -d "Evaluate argument as a script, print the result, then exit" -f -complete -c bun -n "__fish_seen_subcommand_from repl" -s "r" -l "preload" -r -d "Import a module before other modules are loaded" -complete -c bun -n "__fish_seen_subcommand_from repl" -l "smol" -d "Use less memory, but run garbage collection more often" -f -complete -c bun -n "__fish_seen_subcommand_from repl" -s "c" -l "config" -r -d "Specify path to Bun config file" -complete -c bun -n "__fish_seen_subcommand_from repl" -l "cwd" -r -d "Absolute path to resolve files & entry points from" -complete -c bun -n "__fish_seen_subcommand_from repl" -l "env-file" -r -d "Load environment variables from the specified file(s)" -complete -c bun -n "__fish_seen_subcommand_from repl" -l "no-env-file" -d "Disable automatic loading of .env files" -f diff --git a/reference/home/.config/fish/completions/dot.fish b/reference/home/.config/fish/completions/dot.fish deleted file mode 100644 index d686b0b..0000000 --- a/reference/home/.config/fish/completions/dot.fish +++ /dev/null @@ -1,34 +0,0 @@ -function __dot_custom_subcommands - echo init - echo help - path basename $HOME/.config/dot/commands/*.fish 2>/dev/null | path change-extension '' - - for d in $HOME/.config/dot/commands/*/ - test -d $d; or continue - set -l name (path basename $d) - test -f $d$name.fish; or continue - echo $name - end -end - -complete -c dot -n __fish_use_subcommand -a "(__dot_custom_subcommands)" - -# --- dot install --- -complete -c dot -n "__fish_seen_subcommand_from install; and not __fish_seen_argument -l restore" -l restore -d "reinstall every package from the saved list" -complete -c dot -n "__fish_seen_subcommand_from install; and not __fish_seen_argument -l no-sync" -l no-sync -d "skip the pacman -Sy database refresh" -complete -c dot -n "__fish_seen_subcommand_from install; and not __fish_seen_argument -l restore" -f -a "(__fish_print_pacman_packages)" - -# --- dot setup --- -complete -c dot -n "__fish_seen_subcommand_from setup; and not __fish_seen_subcommand_from folders help" -f -a folders -d "bring the 8 standard XDG user directories under the short-name convention" -complete -c dot -n "__fish_seen_subcommand_from setup; and not __fish_seen_subcommand_from folders help" -f -a help -d "show usage" -complete -c dot -n "__fish_seen_subcommand_from setup; and __fish_seen_subcommand_from folders" -f -a help -d "show usage" - -# --- dot kde --- -complete -c dot -n "__fish_seen_subcommand_from kde; and not __fish_seen_subcommand_from apply diff save help" -f -a apply -d "push manifest entries onto the live system" -complete -c dot -n "__fish_seen_subcommand_from kde; and not __fish_seen_subcommand_from apply diff save help" -f -a diff -d "scan for settings whose live value differs from its default" -complete -c dot -n "__fish_seen_subcommand_from kde; and not __fish_seen_subcommand_from apply diff save help" -f -a save -d "write live KDE settings into the manifest" -complete -c dot -n "__fish_seen_subcommand_from kde; and not __fish_seen_subcommand_from apply diff save help" -f -a help -d "show usage" -complete -c dot -n "__fish_seen_subcommand_from kde; and __fish_seen_subcommand_from apply diff save" -f -a help -d "show usage" -# Sourced live from the schema mapping table (real .kcfg files), not a -# hardcoded list -- same helper kde.py's own save/refresh logic builds from. -complete -c dot -n "__fish_seen_subcommand_from kde; and __fish_seen_subcommand_from save" -f -a "(python3 $HOME/.config/dot/commands/kde/kde.py complete 2>/dev/null)" diff --git a/reference/home/.config/fish/completions/fishtape.fish b/reference/home/.config/fish/completions/fishtape.fish deleted file mode 100644 index ad81efe..0000000 --- a/reference/home/.config/fish/completions/fishtape.fish +++ /dev/null @@ -1,2 +0,0 @@ -complete --command fishtape --short v --long version --description "Print version" -complete --command fishtape --short h --long help --description "Print help" diff --git a/reference/home/.config/fish/conf.d/aliases.fish b/reference/home/.config/fish/conf.d/aliases.fish deleted file mode 100644 index a0e0c45..0000000 --- a/reference/home/.config/fish/conf.d/aliases.fish +++ /dev/null @@ -1,4 +0,0 @@ -alias cp='cp -v' -alias vi=nvim -alias vim=nvim -alias tmx='tmux new-session -A -s' diff --git a/reference/home/.config/fish/conf.d/env.fish b/reference/home/.config/fish/conf.d/env.fish deleted file mode 100644 index ceeae7e..0000000 --- a/reference/home/.config/fish/conf.d/env.fish +++ /dev/null @@ -1,4 +0,0 @@ -set -gx EDITOR nvim -set -x ANDROID_HOME $HOME/Android/Sdk -fish_add_path $ANDROID_HOME/platform-tools -fish_add_path $ANDROID_HOME/tools/bin diff --git a/reference/home/.config/fish/conf.d/rustup.fish b/reference/home/.config/fish/conf.d/rustup.fish deleted file mode 100644 index e4cb363..0000000 --- a/reference/home/.config/fish/conf.d/rustup.fish +++ /dev/null @@ -1 +0,0 @@ -source "$HOME/.cargo/env.fish" diff --git a/reference/home/.config/fish/config.fish b/reference/home/.config/fish/config.fish deleted file mode 100644 index a3741f1..0000000 --- a/reference/home/.config/fish/config.fish +++ /dev/null @@ -1,17 +0,0 @@ -source /usr/share/cachyos-fish-config/cachyos-config.fish - -set -gx EDITOR nvim -set -gx VISUAL nvim - -# overwrite greeting -# potentially disabling fastfetch -#function fish_greeting -# # smth smth -#end - -# bun -set --export BUN_INSTALL "$HOME/.bun" -set --export PATH $BUN_INSTALL/bin $PATH - -# Pi -fish_add_path "/home/alexion/.local/opt/node-v24.18.0-linux-x64/bin" diff --git a/reference/home/.config/fish/fish_plugins b/reference/home/.config/fish/fish_plugins deleted file mode 100644 index 32526b8..0000000 --- a/reference/home/.config/fish/fish_plugins +++ /dev/null @@ -1 +0,0 @@ -jorgebucaran/fishtape diff --git a/reference/home/.config/fish/functions/dot.fish b/reference/home/.config/fish/functions/dot.fish deleted file mode 100644 index aea4c0d..0000000 --- a/reference/home/.config/fish/functions/dot.fish +++ /dev/null @@ -1,130 +0,0 @@ -function dot --wraps=git --description 'Manage dotfiles via a bare repo checked out over $HOME' - set -l dotfiles_dir $HOME/.dotfiles - - if test "$argv[1]" = init - set -e argv[1] - __dot_init $dotfiles_dir $argv - return $status - end - - if test "$argv[1]" = help - __dot_help - return $status - end - - set -l commands_dir $HOME/.config/dot/commands - set -l command_file $commands_dir/$argv[1].fish - set -l nested_command_file $commands_dir/$argv[1]/$argv[1].fish - - if test -n "$argv[1]" - if test -f "$command_file" - source $command_file - _dot_$argv[1] $argv[2..-1] - return $status - else if test -f "$nested_command_file" - source $nested_command_file - _dot_$argv[1] $argv[2..-1] - return $status - end - end - - git --git-dir=$dotfiles_dir --work-tree=$HOME $argv -end - -# Kept inline (not a separate autoloaded function file) because this is the -# only subcommand that must work before the dotfiles repo has been cloned. -function __dot_init - set -l dotfiles_dir $argv[1] - set -e argv[1] - - argparse 'url=' -- $argv - or return 1 - - set -l url $_flag_url - test -n "$url"; or set url ssh://gitea@git.alexion.dev:2022/alexion/dotfiles.git - - if test -e $dotfiles_dir - echo "dot init: $dotfiles_dir already exists, refusing to re-initialize" >&2 - return 1 - end - - git clone --bare $url $dotfiles_dir - or begin - echo "dot init: failed to clone $url" >&2 - return 1 - end - - git --git-dir=$dotfiles_dir config status.showUntrackedFiles no - - set -l checkout_output (git --git-dir=$dotfiles_dir --work-tree=$HOME checkout 2>&1) - set -l checkout_status $status - - if test $checkout_status -ne 0 - set -l conflicts - set -l in_block 0 - - for line in $checkout_output - if test $in_block -eq 1 - if string match -rq '^\s' -- $line - set -a conflicts (string trim -- $line) - continue - else - set in_block 0 - end - end - - string match -q '*would be overwritten by checkout:*' -- $line - and set in_block 1 - end - - if test (count $conflicts) -eq 0 - echo "dot init: checkout failed and no recoverable conflicts were found:" >&2 - printf '%s\n' $checkout_output >&2 - return 1 - end - - set -l backup_dir $HOME/.dotfiles-backup/(date +%Y%m%dT%H%M%S) - for f in $conflicts - mkdir -p (path dirname $backup_dir/$f) - mv $HOME/$f $backup_dir/$f - echo "dot init: backed up ~/$f to $backup_dir/$f" - end - - git --git-dir=$dotfiles_dir --work-tree=$HOME checkout - or begin - echo "dot init: checkout still failing after backing up conflicts, aborting" >&2 - return 1 - end - end - - echo "dot init: bootstrapped $dotfiles_dir from $url" -end - -# The custom-subcommand glob is duplicated (not shared with -# completions/dot.fish) because fish only autoloads a function from a file -# named after that function; a shared helper would go undefined if `dot help` -# ran in a completion context before `dot` itself had ever been sourced. -function __dot_help - echo "dot: manage dotfiles via a bare repo checked out over \$HOME - -Commands: - init bootstrap the dotfiles repo on a new machine - help show this message" - - for f in $HOME/.config/dot/commands/*.fish - test -e $f; or continue - echo " "(path basename $f | path change-extension '') - end - - for d in $HOME/.config/dot/commands/*/ - test -d $d; or continue - set -l name (path basename $d) - test -f $d$name.fish; or continue - echo " $name" - end - - echo " -Run 'dot help' for flags on a specific command. - -Any other command is passed through to git (dot status, dot add, dot commit, dot push, ...)." -end diff --git a/reference/home/.config/fish/functions/fishtape.fish b/reference/home/.config/fish/functions/fishtape.fish deleted file mode 100644 index 7759f92..0000000 --- a/reference/home/.config/fish/functions/fishtape.fish +++ /dev/null @@ -1,116 +0,0 @@ -function fishtape --description "Test scripts, functions, and plugins in Fish" - switch "$argv" - case -v --version - echo "fishtape, version 3.0.1" - case "" -h --help - echo "Usage: fishtape Run test files" - echo "Options:" - echo " -v or --version Print version" - echo " -h or --help Print this help message" - case \* - set --local files (realpath $argv) - - for file in $files - if test ! -f $file - echo "fishtape: Invalid file or file not found: \"$file\"" >&2 - return 1 - end - end - - set --local operators -{n,z,b,c,d,e,f,g,G,k,L,O,p,r,s,S,t,u,w,x} - set --local expectations \ - "a non-zero length string" \ - "a zero length string" \ - "a block device" \ - "a character device" \ - "a directory" \ - "an existing file" \ - "a regular file" \ - "a file with the set-group-ID bit set" \ - "a file with same group ID as the current user" \ - "a file with the sticky bit set" \ - "a symbolic link" \ - "a file owned by the current user" \ - "a named pipe" \ - "a file marked as readable" \ - "a file of size greater than zero" \ - "a socket" \ - "a terminal tty file descriptor" \ - "a file with the set-user-ID bit set" \ - "a file marked as writable" \ - "a file marked as executable" - - set --universal _fishtape_test_number 0 - set --universal _fishtape_test_passed 0 - set --universal _fishtape_test_failed 0 - - function @echo - echo "# $argv" - end - - function @test --argument-names name --inherit-variable operators --inherit-variable expectations - set --erase argv[1] - set --query argv[2] || set --append argv "" - - set _fishtape_test_number (math $_fishtape_test_number + 1) - - if test $argv - set _fishtape_test_passed (math $_fishtape_test_passed + 1) - - echo "ok $_fishtape_test_number $name" - else - if test $argv[1] = "!" - set operator "! " - set expected "not " - set --erase argv[1] - end - - if set --query argv[3] - set operator "$operator"$argv[2] - set expected (string escape -- $argv[3]) - set actual (string escape -- $argv[1]) - else - set operator "$operator"$argv[1] - set expected "$expected"$expectations[(contains --index -- $argv[1] $operators)] - set actual (string escape -- $argv[2]) - end - - set _fishtape_test_failed (math $_fishtape_test_failed + 1) - - status print-stack-trace | - string replace --filter --regex -- "\s+called on line (\d+) of file (.+)" '$2:$1' | - read --local at - - echo "not ok $_fishtape_test_number $name" - echo " ---" - echo " operator: $operator" - echo " expected: $expected" - echo " actual: $actual" - echo " at: $at" - echo " ..." - end - end - - echo TAP version 13 - - for file in $files - fish --init-command=(functions @echo | string collect) --init-command=(functions @test | string collect) $file - end - - echo - echo "1..$_fishtape_test_number" - echo "# pass $_fishtape_test_passed" - test $_fishtape_test_failed -eq 0 && - echo "# ok" || - echo "# fail $_fishtape_test_failed" - - functions --erase @echo @test - - set --local failed $_fishtape_test_failed - set --erase _fishtape_test_number - set --erase _fishtape_test_passed - set --erase _fishtape_test_failed - - test $failed -eq 0 - end -end diff --git a/reference/home/.config/fontconfig/fonts.conf b/reference/home/.config/fontconfig/fonts.conf deleted file mode 100644 index acd04e3..0000000 --- a/reference/home/.config/fontconfig/fonts.conf +++ /dev/null @@ -1,79 +0,0 @@ - - - - - - - - roman - - - - roman - - - - - matrix - - 1 - 0.2 - 0 - 1 - - - - - - oblique - - - - false - - - - - - - medium - - - - bold - - - - true - - - - bold - - - - - true - - - - - hintslight - - - - - true - - - diff --git a/reference/home/.config/gtk-3.0/assets/close-active.svg b/reference/home/.config/gtk-3.0/assets/close-active.svg deleted file mode 100644 index 8c5c4ae..0000000 --- a/reference/home/.config/gtk-3.0/assets/close-active.svg +++ /dev/null @@ -1,33 +0,0 @@ - - -Qt SVG Document -Generated with Qt - - - - - - - - - - - - - - - - - - - - diff --git a/reference/home/.config/gtk-3.0/assets/close-backdrop-active.svg b/reference/home/.config/gtk-3.0/assets/close-backdrop-active.svg deleted file mode 100644 index 97b61cd..0000000 --- a/reference/home/.config/gtk-3.0/assets/close-backdrop-active.svg +++ /dev/null @@ -1,33 +0,0 @@ - - -Qt SVG Document -Generated with Qt - - - - - - - - - - - - - - - - - - - - diff --git a/reference/home/.config/gtk-3.0/assets/close-backdrop-hover.svg b/reference/home/.config/gtk-3.0/assets/close-backdrop-hover.svg deleted file mode 100644 index fff82c1..0000000 --- a/reference/home/.config/gtk-3.0/assets/close-backdrop-hover.svg +++ /dev/null @@ -1,33 +0,0 @@ - - -Qt SVG Document -Generated with Qt - - - - - - - - - - - - - - - - - - - - diff --git a/reference/home/.config/gtk-3.0/assets/close-backdrop-normal.svg b/reference/home/.config/gtk-3.0/assets/close-backdrop-normal.svg deleted file mode 100644 index 686fc75..0000000 --- a/reference/home/.config/gtk-3.0/assets/close-backdrop-normal.svg +++ /dev/null @@ -1,27 +0,0 @@ - - -Qt SVG Document -Generated with Qt - - - - - - - - - - - - - - - - diff --git a/reference/home/.config/gtk-3.0/assets/close-hover.svg b/reference/home/.config/gtk-3.0/assets/close-hover.svg deleted file mode 100644 index fa11728..0000000 --- a/reference/home/.config/gtk-3.0/assets/close-hover.svg +++ /dev/null @@ -1,33 +0,0 @@ - - -Qt SVG Document -Generated with Qt - - - - - - - - - - - - - - - - - - - - diff --git a/reference/home/.config/gtk-3.0/assets/close-normal.svg b/reference/home/.config/gtk-3.0/assets/close-normal.svg deleted file mode 100644 index c9b8281..0000000 --- a/reference/home/.config/gtk-3.0/assets/close-normal.svg +++ /dev/null @@ -1,27 +0,0 @@ - - -Qt SVG Document -Generated with Qt - - - - - - - - - - - - - - - - diff --git a/reference/home/.config/gtk-3.0/assets/maximize-active.svg b/reference/home/.config/gtk-3.0/assets/maximize-active.svg deleted file mode 100644 index 2cb231c..0000000 --- a/reference/home/.config/gtk-3.0/assets/maximize-active.svg +++ /dev/null @@ -1,32 +0,0 @@ - - -Qt SVG Document -Generated with Qt - - - - - - - - - - - - - - - - - - - diff --git a/reference/home/.config/gtk-3.0/assets/maximize-backdrop-active.svg b/reference/home/.config/gtk-3.0/assets/maximize-backdrop-active.svg deleted file mode 100644 index 87452e9..0000000 --- a/reference/home/.config/gtk-3.0/assets/maximize-backdrop-active.svg +++ /dev/null @@ -1,32 +0,0 @@ - - -Qt SVG Document -Generated with Qt - - - - - - - - - - - - - - - - - - - diff --git a/reference/home/.config/gtk-3.0/assets/maximize-backdrop-hover.svg b/reference/home/.config/gtk-3.0/assets/maximize-backdrop-hover.svg deleted file mode 100644 index 41112d5..0000000 --- a/reference/home/.config/gtk-3.0/assets/maximize-backdrop-hover.svg +++ /dev/null @@ -1,32 +0,0 @@ - - -Qt SVG Document -Generated with Qt - - - - - - - - - - - - - - - - - - - diff --git a/reference/home/.config/gtk-3.0/assets/maximize-backdrop-normal.svg b/reference/home/.config/gtk-3.0/assets/maximize-backdrop-normal.svg deleted file mode 100644 index 2022b73..0000000 --- a/reference/home/.config/gtk-3.0/assets/maximize-backdrop-normal.svg +++ /dev/null @@ -1,26 +0,0 @@ - - -Qt SVG Document -Generated with Qt - - - - - - - - - - - - - - - diff --git a/reference/home/.config/gtk-3.0/assets/maximize-hover.svg b/reference/home/.config/gtk-3.0/assets/maximize-hover.svg deleted file mode 100644 index e185b9b..0000000 --- a/reference/home/.config/gtk-3.0/assets/maximize-hover.svg +++ /dev/null @@ -1,32 +0,0 @@ - - -Qt SVG Document -Generated with Qt - - - - - - - - - - - - - - - - - - - diff --git a/reference/home/.config/gtk-3.0/assets/maximize-normal.svg b/reference/home/.config/gtk-3.0/assets/maximize-normal.svg deleted file mode 100644 index 801997e..0000000 --- a/reference/home/.config/gtk-3.0/assets/maximize-normal.svg +++ /dev/null @@ -1,26 +0,0 @@ - - -Qt SVG Document -Generated with Qt - - - - - - - - - - - - - - - diff --git a/reference/home/.config/gtk-3.0/assets/maximized-active.svg b/reference/home/.config/gtk-3.0/assets/maximized-active.svg deleted file mode 100644 index 0d94719..0000000 --- a/reference/home/.config/gtk-3.0/assets/maximized-active.svg +++ /dev/null @@ -1,32 +0,0 @@ - - -Qt SVG Document -Generated with Qt - - - - - - - - - - - - - - - - - - - diff --git a/reference/home/.config/gtk-3.0/assets/maximized-backdrop-active.svg b/reference/home/.config/gtk-3.0/assets/maximized-backdrop-active.svg deleted file mode 100644 index 2ab39cf..0000000 --- a/reference/home/.config/gtk-3.0/assets/maximized-backdrop-active.svg +++ /dev/null @@ -1,32 +0,0 @@ - - -Qt SVG Document -Generated with Qt - - - - - - - - - - - - - - - - - - - diff --git a/reference/home/.config/gtk-3.0/assets/maximized-backdrop-hover.svg b/reference/home/.config/gtk-3.0/assets/maximized-backdrop-hover.svg deleted file mode 100644 index 6ed6b12..0000000 --- a/reference/home/.config/gtk-3.0/assets/maximized-backdrop-hover.svg +++ /dev/null @@ -1,32 +0,0 @@ - - -Qt SVG Document -Generated with Qt - - - - - - - - - - - - - - - - - - - diff --git a/reference/home/.config/gtk-3.0/assets/maximized-backdrop-normal.svg b/reference/home/.config/gtk-3.0/assets/maximized-backdrop-normal.svg deleted file mode 100644 index 2e3594f..0000000 --- a/reference/home/.config/gtk-3.0/assets/maximized-backdrop-normal.svg +++ /dev/null @@ -1,26 +0,0 @@ - - -Qt SVG Document -Generated with Qt - - - - - - - - - - - - - - - diff --git a/reference/home/.config/gtk-3.0/assets/maximized-hover.svg b/reference/home/.config/gtk-3.0/assets/maximized-hover.svg deleted file mode 100644 index 09e39ea..0000000 --- a/reference/home/.config/gtk-3.0/assets/maximized-hover.svg +++ /dev/null @@ -1,32 +0,0 @@ - - -Qt SVG Document -Generated with Qt - - - - - - - - - - - - - - - - - - - diff --git a/reference/home/.config/gtk-3.0/assets/maximized-normal.svg b/reference/home/.config/gtk-3.0/assets/maximized-normal.svg deleted file mode 100644 index bf23e18..0000000 --- a/reference/home/.config/gtk-3.0/assets/maximized-normal.svg +++ /dev/null @@ -1,26 +0,0 @@ - - -Qt SVG Document -Generated with Qt - - - - - - - - - - - - - - - diff --git a/reference/home/.config/gtk-3.0/assets/minimize-active.svg b/reference/home/.config/gtk-3.0/assets/minimize-active.svg deleted file mode 100644 index e5835d0..0000000 --- a/reference/home/.config/gtk-3.0/assets/minimize-active.svg +++ /dev/null @@ -1,32 +0,0 @@ - - -Qt SVG Document -Generated with Qt - - - - - - - - - - - - - - - - - - - diff --git a/reference/home/.config/gtk-3.0/assets/minimize-backdrop-active.svg b/reference/home/.config/gtk-3.0/assets/minimize-backdrop-active.svg deleted file mode 100644 index 5e0b1b7..0000000 --- a/reference/home/.config/gtk-3.0/assets/minimize-backdrop-active.svg +++ /dev/null @@ -1,32 +0,0 @@ - - -Qt SVG Document -Generated with Qt - - - - - - - - - - - - - - - - - - - diff --git a/reference/home/.config/gtk-3.0/assets/minimize-backdrop-hover.svg b/reference/home/.config/gtk-3.0/assets/minimize-backdrop-hover.svg deleted file mode 100644 index 761d383..0000000 --- a/reference/home/.config/gtk-3.0/assets/minimize-backdrop-hover.svg +++ /dev/null @@ -1,32 +0,0 @@ - - -Qt SVG Document -Generated with Qt - - - - - - - - - - - - - - - - - - - diff --git a/reference/home/.config/gtk-3.0/assets/minimize-backdrop-normal.svg b/reference/home/.config/gtk-3.0/assets/minimize-backdrop-normal.svg deleted file mode 100644 index 93226c9..0000000 --- a/reference/home/.config/gtk-3.0/assets/minimize-backdrop-normal.svg +++ /dev/null @@ -1,26 +0,0 @@ - - -Qt SVG Document -Generated with Qt - - - - - - - - - - - - - - - diff --git a/reference/home/.config/gtk-3.0/assets/minimize-hover.svg b/reference/home/.config/gtk-3.0/assets/minimize-hover.svg deleted file mode 100644 index 46853fc..0000000 --- a/reference/home/.config/gtk-3.0/assets/minimize-hover.svg +++ /dev/null @@ -1,32 +0,0 @@ - - -Qt SVG Document -Generated with Qt - - - - - - - - - - - - - - - - - - - diff --git a/reference/home/.config/gtk-3.0/assets/minimize-normal.svg b/reference/home/.config/gtk-3.0/assets/minimize-normal.svg deleted file mode 100644 index 8034b01..0000000 --- a/reference/home/.config/gtk-3.0/assets/minimize-normal.svg +++ /dev/null @@ -1,26 +0,0 @@ - - -Qt SVG Document -Generated with Qt - - - - - - - - - - - - - - - diff --git a/reference/home/.config/gtk-3.0/colors.css b/reference/home/.config/gtk-3.0/colors.css deleted file mode 100644 index 325eb22..0000000 --- a/reference/home/.config/gtk-3.0/colors.css +++ /dev/null @@ -1,84 +0,0 @@ -@define-color borders_breeze #4c4e51; -@define-color content_view_bg_breeze #141618; -@define-color error_color_backdrop_breeze #da4453; -@define-color error_color_breeze #da4453; -@define-color error_color_insensitive_backdrop_breeze #55242b; -@define-color error_color_insensitive_breeze #55242b; -@define-color insensitive_base_color_breeze #131517; -@define-color insensitive_base_fg_color_breeze #606263; -@define-color insensitive_bg_color_breeze #1f2124; -@define-color insensitive_borders_breeze #2d3033; -@define-color insensitive_fg_color_breeze #686a6c; -@define-color insensitive_selected_bg_color_breeze #1f2124; -@define-color insensitive_selected_fg_color_breeze #686a6c; -@define-color insensitive_unfocused_bg_color_breeze #1f2124; -@define-color insensitive_unfocused_fg_color_breeze #686a6c; -@define-color insensitive_unfocused_selected_bg_color_breeze #1f2124; -@define-color insensitive_unfocused_selected_fg_color_breeze #686a6c; -@define-color link_color_breeze #1d99f3; -@define-color link_visited_color_breeze #9b59b6; -@define-color success_color_backdrop_breeze #27ae60; -@define-color success_color_breeze #27ae60; -@define-color success_color_insensitive_backdrop_breeze #19482f; -@define-color success_color_insensitive_breeze #19482f; -@define-color theme_base_color_breeze #141618; -@define-color theme_bg_color_breeze #202326; -@define-color theme_button_background_backdrop_breeze #292c30; -@define-color theme_button_background_backdrop_insensitive_breeze #272a2e; -@define-color theme_button_background_insensitive_breeze #272a2e; -@define-color theme_button_background_normal_breeze #292c30; -@define-color theme_button_decoration_focus_backdrop_breeze #3daee9; -@define-color theme_button_decoration_focus_backdrop_insensitive_breeze #2e556b; -@define-color theme_button_decoration_focus_breeze #3daee9; -@define-color theme_button_decoration_focus_insensitive_breeze #2e556b; -@define-color theme_button_decoration_hover_backdrop_breeze #3daee9; -@define-color theme_button_decoration_hover_backdrop_insensitive_breeze #2e556b; -@define-color theme_button_decoration_hover_breeze #3daee9; -@define-color theme_button_decoration_hover_insensitive_breeze #2e556b; -@define-color theme_button_foreground_active_backdrop_breeze #fcfcfc; -@define-color theme_button_foreground_active_backdrop_insensitive_breeze #686a6c; -@define-color theme_button_foreground_active_breeze #fcfcfc; -@define-color theme_button_foreground_active_insensitive_breeze #686a6c; -@define-color theme_button_foreground_backdrop_breeze #fcfcfc; -@define-color theme_button_foreground_backdrop_insensitive_breeze #6d6f72; -@define-color theme_button_foreground_insensitive_breeze #6d6f72; -@define-color theme_button_foreground_normal_breeze #fcfcfc; -@define-color theme_fg_color_breeze #fcfcfc; -@define-color theme_header_background_backdrop_breeze #202326; -@define-color theme_header_background_breeze #292c30; -@define-color theme_header_background_light_breeze #202326; -@define-color theme_header_foreground_backdrop_breeze #fcfcfc; -@define-color theme_header_foreground_breeze #fcfcfc; -@define-color theme_header_foreground_insensitive_backdrop_breeze #fcfcfc; -@define-color theme_header_foreground_insensitive_breeze #fcfcfc; -@define-color theme_hovering_selected_bg_color_breeze #3daee9; -@define-color theme_selected_bg_color_breeze #3daee9; -@define-color theme_selected_fg_color_breeze #fcfcfc; -@define-color theme_text_color_breeze #fcfcfc; -@define-color theme_titlebar_background_backdrop_breeze #202326; -@define-color theme_titlebar_background_breeze #292c30; -@define-color theme_titlebar_background_light_breeze #202326; -@define-color theme_titlebar_foreground_backdrop_breeze #fcfcfc; -@define-color theme_titlebar_foreground_breeze #fcfcfc; -@define-color theme_titlebar_foreground_insensitive_backdrop_breeze #fcfcfc; -@define-color theme_titlebar_foreground_insensitive_breeze #fcfcfc; -@define-color theme_unfocused_base_color_breeze #141618; -@define-color theme_unfocused_bg_color_breeze #202326; -@define-color theme_unfocused_fg_color_breeze #fcfcfc; -@define-color theme_unfocused_selected_bg_color_alt_breeze #1b4155; -@define-color theme_unfocused_selected_bg_color_breeze #1b4155; -@define-color theme_unfocused_selected_fg_color_breeze #fcfcfc; -@define-color theme_unfocused_text_color_breeze #fcfcfc; -@define-color theme_unfocused_view_bg_color_breeze #131517; -@define-color theme_unfocused_view_text_color_breeze #606263; -@define-color theme_view_active_decoration_color_breeze #3daee9; -@define-color theme_view_hover_decoration_color_breeze #3daee9; -@define-color tooltip_background_breeze #292c30; -@define-color tooltip_border_breeze #535659; -@define-color tooltip_text_breeze #fcfcfc; -@define-color unfocused_borders_breeze #4c4e51; -@define-color unfocused_insensitive_borders_breeze #2d3033; -@define-color warning_color_backdrop_breeze #f67400; -@define-color warning_color_breeze #f67400; -@define-color warning_color_insensitive_backdrop_breeze #5e340f; -@define-color warning_color_insensitive_breeze #5e340f; diff --git a/reference/home/.config/gtk-3.0/gtk.css b/reference/home/.config/gtk-3.0/gtk.css deleted file mode 100644 index c9763f7..0000000 --- a/reference/home/.config/gtk-3.0/gtk.css +++ /dev/null @@ -1 +0,0 @@ -@import 'colors.css'; \ No newline at end of file diff --git a/reference/home/.config/gtk-3.0/settings.ini b/reference/home/.config/gtk-3.0/settings.ini deleted file mode 100644 index 15264ab..0000000 --- a/reference/home/.config/gtk-3.0/settings.ini +++ /dev/null @@ -1,18 +0,0 @@ -[Settings] -gtk-application-prefer-dark-theme=true -gtk-button-images=true -gtk-cursor-blink=true -gtk-cursor-blink-time=1000 -gtk-cursor-theme-name=breeze_cursors -gtk-cursor-theme-size=24 -gtk-decoration-layout=icon:minimize,maximize,close -gtk-enable-animations=true -gtk-font-name=Noto Sans, 14 -gtk-icon-theme-name=breeze-dark -gtk-menu-images=true -gtk-modules=colorreload-gtk-module:window-decorations-gtk-module -gtk-primary-button-warps-slider=true -gtk-sound-theme-name=ocean -gtk-theme-name=Breeze -gtk-toolbar-style=3 -gtk-xft-dpi=98304 diff --git a/reference/home/.config/gtk-3.0/window_decorations.css b/reference/home/.config/gtk-3.0/window_decorations.css deleted file mode 100644 index ca70a8a..0000000 --- a/reference/home/.config/gtk-3.0/window_decorations.css +++ /dev/null @@ -1,71 +0,0 @@ -headerbar button.titlebutton.close, .titlebar button.titlebutton.close { - background-image: url("assets/close-normal.svg"); } - -headerbar button.titlebutton.close:hover, .titlebar button.titlebutton.close:hover { - background-image: url("assets/close-hover.svg"); } - -headerbar button.titlebutton.close:active, .titlebar button.titlebutton.close:active { - background-image: url("assets/close-active.svg"); } - -headerbar button.titlebutton.close:backdrop, .titlebar button.titlebutton.close:backdrop { - background-image: url("assets/close-backdrop-normal.svg"); } - -headerbar button.titlebutton.close:backdrop:hover, .titlebar button.titlebutton.close:backdrop:hover { - background-image: url("assets/close-backdrop-hover.svg"); } - -headerbar button.titlebutton.close:backdrop:active, .titlebar button.titlebutton.close:backdrop:active { - background-image: url("assets/close-backdrop-active.svg"); } - -headerbar button.titlebutton.maximize, .titlebar button.titlebutton.maximize { - background-image: url("assets/maximize-normal.svg"); } - -headerbar button.titlebutton.maximize:hover, .titlebar button.titlebutton.maximize:hover { - background-image: url("assets/maximize-hover.svg"); } - -headerbar button.titlebutton.maximize:active, .titlebar button.titlebutton.maximize:active { - background-image: url("assets/maximize-active.svg"); } - -headerbar button.titlebutton.maximize:backdrop, .titlebar button.titlebutton.maximize:backdrop { - background-image: url("assets/maximize-backdrop-normal.svg"); } - -headerbar button.titlebutton.maximize:backdrop:hover, .titlebar button.titlebutton.maximize:backdrop:hover { - background-image: url("assets/maximize-backdrop-hover.svg"); } - -headerbar button.titlebutton.maximize:backdrop:active, .titlebar button.titlebutton.maximize:backdrop:active { - background-image: url("assets/maximize-backdrop-active.svg"); } - -headerbar button.titlebutton.minimize, .titlebar button.titlebutton.minimize { - background-image: url("assets/minimize-normal.svg"); } - -headerbar button.titlebutton.minimize:hover, .titlebar button.titlebutton.minimize:hover { - background-image: url("assets/minimize-hover.svg"); } - -headerbar button.titlebutton.minimize:active, .titlebar button.titlebutton.minimize:active { - background-image: url("assets/minimize-active.svg"); } - -headerbar button.titlebutton.minimize:backdrop, .titlebar button.titlebutton.minimize:backdrop { - background-image: url("assets/minimize-backdrop-normal.svg"); } - -headerbar button.titlebutton.minimize:backdrop:hover, .titlebar button.titlebutton.minimize:backdrop:hover { - background-image: url("assets/minimize-backdrop-hover.svg"); } - -headerbar button.titlebutton.minimize:backdrop:active, .titlebar button.titlebutton.minimize:backdrop:active { - background-image: url("assets/minimize-backdrop-active.svg"); } - -.maximized headerbar button.titlebutton.maximize, .maximized .titlebar button.titlebutton.maximize { - background-image: url("assets/maximized-normal.svg"); } - -.maximized headerbar button.titlebutton.maximize:hover, .maximized .titlebar button.titlebutton.maximize:hover { - background-image: url("assets/maximized-hover.svg"); } - -.maximized headerbar button.titlebutton.maximize:active, .maximized .titlebar button.titlebutton.maximize:active { - background-image: url("assets/maximized-active.svg"); } - -.maximized headerbar button.titlebutton.maximize:backdrop, .maximized .titlebar button.titlebutton.maximize:backdrop { - background-image: url("assets/maximized-backdrop-normal.svg"); } - -.maximized headerbar button.titlebutton.maximize:backdrop:hover, .maximized .titlebar button.titlebutton.maximize:backdrop:hover { - background-image: url("assets/maximized-backdrop-hover.svg"); } - -.maximized headerbar button.titlebutton.maximize:backdrop:active, .maximized .titlebar button.titlebutton.maximize:backdrop:active { - background-image: url("assets/maximized-backdrop-active.svg"); } diff --git a/reference/home/.config/gtk-4.0/colors.css b/reference/home/.config/gtk-4.0/colors.css deleted file mode 100644 index 325eb22..0000000 --- a/reference/home/.config/gtk-4.0/colors.css +++ /dev/null @@ -1,84 +0,0 @@ -@define-color borders_breeze #4c4e51; -@define-color content_view_bg_breeze #141618; -@define-color error_color_backdrop_breeze #da4453; -@define-color error_color_breeze #da4453; -@define-color error_color_insensitive_backdrop_breeze #55242b; -@define-color error_color_insensitive_breeze #55242b; -@define-color insensitive_base_color_breeze #131517; -@define-color insensitive_base_fg_color_breeze #606263; -@define-color insensitive_bg_color_breeze #1f2124; -@define-color insensitive_borders_breeze #2d3033; -@define-color insensitive_fg_color_breeze #686a6c; -@define-color insensitive_selected_bg_color_breeze #1f2124; -@define-color insensitive_selected_fg_color_breeze #686a6c; -@define-color insensitive_unfocused_bg_color_breeze #1f2124; -@define-color insensitive_unfocused_fg_color_breeze #686a6c; -@define-color insensitive_unfocused_selected_bg_color_breeze #1f2124; -@define-color insensitive_unfocused_selected_fg_color_breeze #686a6c; -@define-color link_color_breeze #1d99f3; -@define-color link_visited_color_breeze #9b59b6; -@define-color success_color_backdrop_breeze #27ae60; -@define-color success_color_breeze #27ae60; -@define-color success_color_insensitive_backdrop_breeze #19482f; -@define-color success_color_insensitive_breeze #19482f; -@define-color theme_base_color_breeze #141618; -@define-color theme_bg_color_breeze #202326; -@define-color theme_button_background_backdrop_breeze #292c30; -@define-color theme_button_background_backdrop_insensitive_breeze #272a2e; -@define-color theme_button_background_insensitive_breeze #272a2e; -@define-color theme_button_background_normal_breeze #292c30; -@define-color theme_button_decoration_focus_backdrop_breeze #3daee9; -@define-color theme_button_decoration_focus_backdrop_insensitive_breeze #2e556b; -@define-color theme_button_decoration_focus_breeze #3daee9; -@define-color theme_button_decoration_focus_insensitive_breeze #2e556b; -@define-color theme_button_decoration_hover_backdrop_breeze #3daee9; -@define-color theme_button_decoration_hover_backdrop_insensitive_breeze #2e556b; -@define-color theme_button_decoration_hover_breeze #3daee9; -@define-color theme_button_decoration_hover_insensitive_breeze #2e556b; -@define-color theme_button_foreground_active_backdrop_breeze #fcfcfc; -@define-color theme_button_foreground_active_backdrop_insensitive_breeze #686a6c; -@define-color theme_button_foreground_active_breeze #fcfcfc; -@define-color theme_button_foreground_active_insensitive_breeze #686a6c; -@define-color theme_button_foreground_backdrop_breeze #fcfcfc; -@define-color theme_button_foreground_backdrop_insensitive_breeze #6d6f72; -@define-color theme_button_foreground_insensitive_breeze #6d6f72; -@define-color theme_button_foreground_normal_breeze #fcfcfc; -@define-color theme_fg_color_breeze #fcfcfc; -@define-color theme_header_background_backdrop_breeze #202326; -@define-color theme_header_background_breeze #292c30; -@define-color theme_header_background_light_breeze #202326; -@define-color theme_header_foreground_backdrop_breeze #fcfcfc; -@define-color theme_header_foreground_breeze #fcfcfc; -@define-color theme_header_foreground_insensitive_backdrop_breeze #fcfcfc; -@define-color theme_header_foreground_insensitive_breeze #fcfcfc; -@define-color theme_hovering_selected_bg_color_breeze #3daee9; -@define-color theme_selected_bg_color_breeze #3daee9; -@define-color theme_selected_fg_color_breeze #fcfcfc; -@define-color theme_text_color_breeze #fcfcfc; -@define-color theme_titlebar_background_backdrop_breeze #202326; -@define-color theme_titlebar_background_breeze #292c30; -@define-color theme_titlebar_background_light_breeze #202326; -@define-color theme_titlebar_foreground_backdrop_breeze #fcfcfc; -@define-color theme_titlebar_foreground_breeze #fcfcfc; -@define-color theme_titlebar_foreground_insensitive_backdrop_breeze #fcfcfc; -@define-color theme_titlebar_foreground_insensitive_breeze #fcfcfc; -@define-color theme_unfocused_base_color_breeze #141618; -@define-color theme_unfocused_bg_color_breeze #202326; -@define-color theme_unfocused_fg_color_breeze #fcfcfc; -@define-color theme_unfocused_selected_bg_color_alt_breeze #1b4155; -@define-color theme_unfocused_selected_bg_color_breeze #1b4155; -@define-color theme_unfocused_selected_fg_color_breeze #fcfcfc; -@define-color theme_unfocused_text_color_breeze #fcfcfc; -@define-color theme_unfocused_view_bg_color_breeze #131517; -@define-color theme_unfocused_view_text_color_breeze #606263; -@define-color theme_view_active_decoration_color_breeze #3daee9; -@define-color theme_view_hover_decoration_color_breeze #3daee9; -@define-color tooltip_background_breeze #292c30; -@define-color tooltip_border_breeze #535659; -@define-color tooltip_text_breeze #fcfcfc; -@define-color unfocused_borders_breeze #4c4e51; -@define-color unfocused_insensitive_borders_breeze #2d3033; -@define-color warning_color_backdrop_breeze #f67400; -@define-color warning_color_breeze #f67400; -@define-color warning_color_insensitive_backdrop_breeze #5e340f; -@define-color warning_color_insensitive_breeze #5e340f; diff --git a/reference/home/.config/gtk-4.0/gtk.css b/reference/home/.config/gtk-4.0/gtk.css deleted file mode 100644 index c9763f7..0000000 --- a/reference/home/.config/gtk-4.0/gtk.css +++ /dev/null @@ -1 +0,0 @@ -@import 'colors.css'; \ No newline at end of file diff --git a/reference/home/.config/gtk-4.0/settings.ini b/reference/home/.config/gtk-4.0/settings.ini deleted file mode 100644 index b4bf85a..0000000 --- a/reference/home/.config/gtk-4.0/settings.ini +++ /dev/null @@ -1,13 +0,0 @@ -[Settings] -gtk-application-prefer-dark-theme=true -gtk-cursor-blink=true -gtk-cursor-blink-time=1000 -gtk-cursor-theme-name=breeze_cursors -gtk-cursor-theme-size=24 -gtk-decoration-layout=icon:minimize,maximize,close -gtk-enable-animations=true -gtk-font-name=Noto Sans, 14 -gtk-icon-theme-name=breeze-dark -gtk-primary-button-warps-slider=true -gtk-sound-theme-name=ocean -gtk-xft-dpi=98304 diff --git a/reference/home/.config/gtk-4.0/window_decorations.css b/reference/home/.config/gtk-4.0/window_decorations.css deleted file mode 100644 index ca70a8a..0000000 --- a/reference/home/.config/gtk-4.0/window_decorations.css +++ /dev/null @@ -1,71 +0,0 @@ -headerbar button.titlebutton.close, .titlebar button.titlebutton.close { - background-image: url("assets/close-normal.svg"); } - -headerbar button.titlebutton.close:hover, .titlebar button.titlebutton.close:hover { - background-image: url("assets/close-hover.svg"); } - -headerbar button.titlebutton.close:active, .titlebar button.titlebutton.close:active { - background-image: url("assets/close-active.svg"); } - -headerbar button.titlebutton.close:backdrop, .titlebar button.titlebutton.close:backdrop { - background-image: url("assets/close-backdrop-normal.svg"); } - -headerbar button.titlebutton.close:backdrop:hover, .titlebar button.titlebutton.close:backdrop:hover { - background-image: url("assets/close-backdrop-hover.svg"); } - -headerbar button.titlebutton.close:backdrop:active, .titlebar button.titlebutton.close:backdrop:active { - background-image: url("assets/close-backdrop-active.svg"); } - -headerbar button.titlebutton.maximize, .titlebar button.titlebutton.maximize { - background-image: url("assets/maximize-normal.svg"); } - -headerbar button.titlebutton.maximize:hover, .titlebar button.titlebutton.maximize:hover { - background-image: url("assets/maximize-hover.svg"); } - -headerbar button.titlebutton.maximize:active, .titlebar button.titlebutton.maximize:active { - background-image: url("assets/maximize-active.svg"); } - -headerbar button.titlebutton.maximize:backdrop, .titlebar button.titlebutton.maximize:backdrop { - background-image: url("assets/maximize-backdrop-normal.svg"); } - -headerbar button.titlebutton.maximize:backdrop:hover, .titlebar button.titlebutton.maximize:backdrop:hover { - background-image: url("assets/maximize-backdrop-hover.svg"); } - -headerbar button.titlebutton.maximize:backdrop:active, .titlebar button.titlebutton.maximize:backdrop:active { - background-image: url("assets/maximize-backdrop-active.svg"); } - -headerbar button.titlebutton.minimize, .titlebar button.titlebutton.minimize { - background-image: url("assets/minimize-normal.svg"); } - -headerbar button.titlebutton.minimize:hover, .titlebar button.titlebutton.minimize:hover { - background-image: url("assets/minimize-hover.svg"); } - -headerbar button.titlebutton.minimize:active, .titlebar button.titlebutton.minimize:active { - background-image: url("assets/minimize-active.svg"); } - -headerbar button.titlebutton.minimize:backdrop, .titlebar button.titlebutton.minimize:backdrop { - background-image: url("assets/minimize-backdrop-normal.svg"); } - -headerbar button.titlebutton.minimize:backdrop:hover, .titlebar button.titlebutton.minimize:backdrop:hover { - background-image: url("assets/minimize-backdrop-hover.svg"); } - -headerbar button.titlebutton.minimize:backdrop:active, .titlebar button.titlebutton.minimize:backdrop:active { - background-image: url("assets/minimize-backdrop-active.svg"); } - -.maximized headerbar button.titlebutton.maximize, .maximized .titlebar button.titlebutton.maximize { - background-image: url("assets/maximized-normal.svg"); } - -.maximized headerbar button.titlebutton.maximize:hover, .maximized .titlebar button.titlebutton.maximize:hover { - background-image: url("assets/maximized-hover.svg"); } - -.maximized headerbar button.titlebutton.maximize:active, .maximized .titlebar button.titlebutton.maximize:active { - background-image: url("assets/maximized-active.svg"); } - -.maximized headerbar button.titlebutton.maximize:backdrop, .maximized .titlebar button.titlebutton.maximize:backdrop { - background-image: url("assets/maximized-backdrop-normal.svg"); } - -.maximized headerbar button.titlebutton.maximize:backdrop:hover, .maximized .titlebar button.titlebutton.maximize:backdrop:hover { - background-image: url("assets/maximized-backdrop-hover.svg"); } - -.maximized headerbar button.titlebutton.maximize:backdrop:active, .maximized .titlebar button.titlebutton.maximize:backdrop:active { - background-image: url("assets/maximized-backdrop-active.svg"); } diff --git a/reference/home/.config/gtkrc-2.0 b/reference/home/.config/gtkrc-2.0 deleted file mode 100644 index 638be81..0000000 --- a/reference/home/.config/gtkrc-2.0 +++ /dev/null @@ -1,5 +0,0 @@ -# created by KDE Plasma, Sun Jul 12 19:24:12 2026 -# - -gtk-alternative-button-order = 1 - diff --git a/reference/home/.config/kcminputrc b/reference/home/.config/kcminputrc deleted file mode 100644 index ad1d422..0000000 --- a/reference/home/.config/kcminputrc +++ /dev/null @@ -1,2 +0,0 @@ -[Libinput][1739][30383][DELL08AF:00 06CB:76AF Touchpad] -ClickMethod=2 diff --git a/reference/home/.config/kdeglobals b/reference/home/.config/kdeglobals deleted file mode 100644 index 2f4bcb9..0000000 --- a/reference/home/.config/kdeglobals +++ /dev/null @@ -1,177 +0,0 @@ -[ColorEffects:Disabled] -ChangeSelectionColor= -Color=56,56,56 -ColorAmount=0 -ColorEffect=0 -ContrastAmount=0.65 -ContrastEffect=1 -Enable= -IntensityAmount=0.1 -IntensityEffect=2 - -[ColorEffects:Inactive] -ChangeSelectionColor=true -Color=112,111,110 -ColorAmount=0.025 -ColorEffect=2 -ContrastAmount=0.1 -ContrastEffect=2 -Enable=false -IntensityAmount=0 -IntensityEffect=0 - -[Colors:Button] -BackgroundAlternate=30,87,116 -BackgroundNormal=41,44,48 -DecorationFocus=61,174,233 -DecorationHover=61,174,233 -ForegroundActive=61,174,233 -ForegroundInactive=161,169,177 -ForegroundLink=29,153,243 -ForegroundNegative=218,68,83 -ForegroundNeutral=246,116,0 -ForegroundNormal=252,252,252 -ForegroundPositive=39,174,96 -ForegroundVisited=155,89,182 - -[Colors:Complementary] -BackgroundAlternate=30,87,116 -BackgroundNormal=32,35,38 -DecorationFocus=61,174,233 -DecorationHover=61,174,233 -ForegroundActive=61,174,233 -ForegroundInactive=161,169,177 -ForegroundLink=29,153,243 -ForegroundNegative=218,68,83 -ForegroundNeutral=246,116,0 -ForegroundNormal=252,252,252 -ForegroundPositive=39,174,96 -ForegroundVisited=155,89,182 - -[Colors:Header] -BackgroundAlternate=32,35,38 -BackgroundNormal=41,44,48 -DecorationFocus=61,174,233 -DecorationHover=61,174,233 -ForegroundActive=61,174,233 -ForegroundInactive=161,169,177 -ForegroundLink=29,153,243 -ForegroundNegative=218,68,83 -ForegroundNeutral=246,116,0 -ForegroundNormal=252,252,252 -ForegroundPositive=39,174,96 -ForegroundVisited=155,89,182 - -[Colors:Header][Inactive] -BackgroundAlternate=41,44,48 -BackgroundNormal=32,35,38 -DecorationFocus=61,174,233 -DecorationHover=61,174,233 -ForegroundActive=61,174,233 -ForegroundInactive=161,169,177 -ForegroundLink=29,153,243 -ForegroundNegative=218,68,83 -ForegroundNeutral=246,116,0 -ForegroundNormal=252,252,252 -ForegroundPositive=39,174,96 -ForegroundVisited=155,89,182 - -[Colors:Selection] -BackgroundAlternate=30,87,116 -BackgroundNormal=61,174,233 -DecorationFocus=61,174,233 -DecorationHover=61,174,233 -ForegroundActive=252,252,252 -ForegroundInactive=161,169,177 -ForegroundLink=253,188,75 -ForegroundNegative=176,55,69 -ForegroundNeutral=198,92,0 -ForegroundNormal=252,252,252 -ForegroundPositive=23,104,57 -ForegroundVisited=155,89,182 - -[Colors:Tooltip] -BackgroundAlternate=32,35,38 -BackgroundNormal=41,44,48 -DecorationFocus=61,174,233 -DecorationHover=61,174,233 -ForegroundActive=61,174,233 -ForegroundInactive=161,169,177 -ForegroundLink=29,153,243 -ForegroundNegative=218,68,83 -ForegroundNeutral=246,116,0 -ForegroundNormal=252,252,252 -ForegroundPositive=39,174,96 -ForegroundVisited=155,89,182 - -[Colors:View] -BackgroundAlternate=29,31,34 -BackgroundNormal=20,22,24 -DecorationFocus=61,174,233 -DecorationHover=61,174,233 -ForegroundActive=61,174,233 -ForegroundInactive=161,169,177 -ForegroundLink=29,153,243 -ForegroundNegative=218,68,83 -ForegroundNeutral=246,116,0 -ForegroundNormal=252,252,252 -ForegroundPositive=39,174,96 -ForegroundVisited=155,89,182 - -[Colors:Window] -BackgroundAlternate=41,44,48 -BackgroundNormal=32,35,38 -DecorationFocus=61,174,233 -DecorationHover=61,174,233 -ForegroundActive=61,174,233 -ForegroundInactive=161,169,177 -ForegroundLink=29,153,243 -ForegroundNegative=218,68,83 -ForegroundNeutral=246,116,0 -ForegroundNormal=252,252,252 -ForegroundPositive=39,174,96 -ForegroundVisited=155,89,182 - -[General] -ColorSchemeHash=9d38eb71ded8ff549da26d57d7cbab9164da243a -UseSystemBell=true -XftAntialias=true -XftHintStyle=hintslight -XftSubPixel=none -fixed=Hack,14,-1,5,400,0,0,0,0,0,0,0,0,0,0,1,,0,0 -font=Noto Sans,14,-1,5,400,0,0,0,0,0,0,0,0,0,0,1,,0,0 -menuFont=Noto Sans,14,-1,5,400,0,0,0,0,0,0,0,0,0,0,1,,0,0 -smallestReadableFont=Noto Sans,12,-1,5,400,0,0,0,0,0,0,0,0,0,0,1,,0,0 -toolBarFont=Noto Sans,14,-1,5,400,0,0,0,0,0,0,0,0,0,0,1,,0,0 - -[KDE] -AnimationDurationFactor=0.25 -LookAndFeelPackage=org.kde.breezedark.desktop -contrast=4 -frameContrast=0.2 - -[KFileDialog Settings] -Allow Expansion=false -Automatically select filename extension=true -Breadcrumb Navigation=true -Decoration position=2 -Show Full Path=false -Show Inline Previews=true -Show Preview=false -Show Speedbar=true -Show hidden files=false -Sort by=Name -Sort directories first=true -Sort hidden files last=false -Sort reversed=false -Speedbar Width=140 -View Style=DetailTree - -[WM] -activeBackground=39,44,49 -activeBlend=252,252,252 -activeFont=Noto Sans,14,-1,5,400,0,0,0,0,0,0,0,0,0,0,1,,0,0 -activeForeground=252,252,252 -inactiveBackground=32,36,40 -inactiveBlend=161,169,177 -inactiveForeground=161,169,177 diff --git a/reference/home/.config/kglobalshortcutsrc b/reference/home/.config/kglobalshortcutsrc deleted file mode 100644 index 06ff1e1..0000000 --- a/reference/home/.config/kglobalshortcutsrc +++ /dev/null @@ -1,277 +0,0 @@ -[ActivityManager] -_k_friendly_name=Activity Manager -switch-to-activity-4dfe7a63-75b1-4532-89bb-9e8ebc7e360d=none,none,Switch to activity "Default" - -[KDE Keyboard Layout Switcher] -Switch to Last-Used Keyboard Layout=Meta+Alt+L,Meta+Alt+L,Switch to Last-Used Keyboard Layout -Switch to Next Keyboard Layout=Meta+Alt+K,Meta+Alt+K,Switch to Next Keyboard Layout -_k_friendly_name=Keyboard Layout Switcher - -[kaccess] -Toggle Screen Reader On and Off=Meta+Alt+S,Meta+Alt+S,Toggle Screen Reader On and Off -_k_friendly_name=Accessibility - -[kmix] -_k_friendly_name=Audio Volume -decrease_microphone_volume=Microphone Volume Down,Microphone Volume Down,Decrease Microphone Volume -decrease_volume=Volume Down,Volume Down,Decrease Volume -decrease_volume_small=Shift+Volume Down,Shift+Volume Down,Decrease Volume by 1% -increase_microphone_volume=Microphone Volume Up,Microphone Volume Up,Increase Microphone Volume -increase_volume=Volume Up,Volume Up,Increase Volume -increase_volume_small=Shift+Volume Up,Shift+Volume Up,Increase Volume by 1% -mic_mute=Microphone Mute\tMeta+Volume Mute,Microphone Mute\tMeta+Volume Mute,Mute Microphone -mute=Volume Mute,Volume Mute,Mute -push_to_talk=none,none,Push to talk - -[ksmserver] -Halt Without Confirmation=none,none,Shut Down Without Confirmation -Lock Session=Screensaver\tMeta+L,Screensaver\tMeta+L,Lock Session -Log Out=Ctrl+Alt+Del,Ctrl+Alt+Del,Show Logout Screen -Log Out Without Confirmation=none,none,Log Out Without Confirmation -LogOut=none,none,Log Out -Reboot=none,none,Reboot -Reboot Without Confirmation=none,none,Reboot Without Confirmation -Shut Down=none,none,Shut Down -_k_friendly_name=Session Management - -[kwin] -Activate Window Demanding Attention=Meta+Ctrl+A,Meta+Ctrl+A,Activate Window Demanding Attention -Cycle Overview=none,none,Cycle through Overview and Grid View -Cycle Overview Opposite=none,none,Cycle through Grid View and Overview -Decrease Opacity=none,none,Decrease Opacity of Active Window by 5% -Edit Tiles=Meta+T,Meta+T,Toggle Tiles Editor -Expose=Ctrl+F9\tMeta+F9,Ctrl+F9\tMeta+F9,Toggle Present Windows (Current desktop) -ExposeAll=Launch (C)\tCtrl+F10\tMeta+F10,Launch (C)\tCtrl+F10\tMeta+F10,Toggle Present Windows (All desktops) -ExposeClass=Ctrl+F7\tMeta+F7,Ctrl+F7\tMeta+F7,Toggle Present Windows (Window class) -ExposeClassCurrentDesktop=none,none,Toggle Present Windows (Window class on current desktop) -Grid View=Meta+G,Meta+G,Toggle Grid View -Increase Opacity=none,none,Increase Opacity of Active Window by 5% -Kill Window=Meta+Ctrl+Esc,Meta+Ctrl+Esc,Kill Window -Move Tablet to Next LogicalOutput=none,none,Move the tablet to the next output -MoveMouseToCenter=Meta+F6,Meta+F6,Move Mouse to Centre -MoveMouseToFocus=Meta+F5,Meta+F5,Move Mouse to Focus -MoveZoomDown=none,none,Move Zoomed Area Downwards -MoveZoomLeft=none,none,Move Zoomed Area to Left -MoveZoomRight=none,none,Move Zoomed Area to Right -MoveZoomUp=none,none,Move Zoomed Area Upwards -Overview=Meta+W,Meta+W,Toggle Overview -Setup Window Shortcut=none,none,Setup Window Shortcut -Show Desktop=Meta+D,Meta+D,Peek at Desktop -Switch One Desktop Down=Meta+Ctrl+Down,Meta+Ctrl+Down,Switch One Desktop Down -Switch One Desktop Up=Meta+Ctrl+Up,Meta+Ctrl+Up,Switch One Desktop Up -Switch One Desktop to the Left=Meta+Ctrl+Left,Meta+Ctrl+Left,Switch One Desktop to the Left -Switch One Desktop to the Right=Meta+Ctrl+Right,Meta+Ctrl+Right,Switch One Desktop to the Right -Switch Window Down=Meta+Alt+Down,Meta+Alt+Down,Switch to Window Below -Switch Window Left=Meta+Alt+Left,Meta+Alt+Left,Switch to Window to the Left -Switch Window Right=Meta+Alt+Right,Meta+Alt+Right,Switch to Window to the Right -Switch Window Up=Meta+Alt+Up,Meta+Alt+Up,Switch to Window Above -Switch to Desktop 1=Meta+1,Ctrl+F1\tMeta+F1,Switch to Desktop 1 -Switch to Desktop 10=none,none,Switch to Desktop 10 -Switch to Desktop 11=none,none,Switch to Desktop 11 -Switch to Desktop 12=none,none,Switch to Desktop 12 -Switch to Desktop 13=none,none,Switch to Desktop 13 -Switch to Desktop 14=none,none,Switch to Desktop 14 -Switch to Desktop 15=none,none,Switch to Desktop 15 -Switch to Desktop 16=none,none,Switch to Desktop 16 -Switch to Desktop 17=none,none,Switch to Desktop 17 -Switch to Desktop 18=none,none,Switch to Desktop 18 -Switch to Desktop 19=none,none,Switch to Desktop 19 -Switch to Desktop 2=Meta+2,Ctrl+F2\tMeta+F2,Switch to Desktop 2 -Switch to Desktop 20=none,none,Switch to Desktop 20 -Switch to Desktop 21=none,none,Switch to Desktop 21 -Switch to Desktop 22=none,none,Switch to Desktop 22 -Switch to Desktop 23=none,none,Switch to Desktop 23 -Switch to Desktop 24=none,none,Switch to Desktop 24 -Switch to Desktop 25=none,none,Switch to Desktop 25 -Switch to Desktop 3=Meta+3,Ctrl+F3\tMeta+F3,Switch to Desktop 3 -Switch to Desktop 4=Meta+4,Ctrl+F4\tMeta+F4,Switch to Desktop 4 -Switch to Desktop 5=Meta+5,none,Switch to Desktop 5 -Switch to Desktop 6=Meta+6,none,Switch to Desktop 6 -Switch to Desktop 7=Meta+7,none,Switch to Desktop 7 -Switch to Desktop 8=Meta+8,none,Switch to Desktop 8 -Switch to Desktop 9=Meta+9,none,Switch to Desktop 9 -Switch to Next Desktop=none,none,Switch to Next Desktop -Switch to Next Screen=none,none,Switch to Next Screen -Switch to Previous Desktop=none,none,Switch to Previous Desktop -Switch to Previous Screen=none,none,Switch to Previous Screen -Switch to Screen 0=none,none,Switch to Screen 0 -Switch to Screen 1=none,none,Switch to Screen 1 -Switch to Screen 2=none,none,Switch to Screen 2 -Switch to Screen 3=none,none,Switch to Screen 3 -Switch to Screen 4=none,none,Switch to Screen 4 -Switch to Screen 5=none,none,Switch to Screen 5 -Switch to Screen 6=none,none,Switch to Screen 6 -Switch to Screen 7=none,none,Switch to Screen 7 -Switch to Screen Above=none,none,Switch to Screen Above -Switch to Screen Below=none,none,Switch to Screen Below -Switch to Screen to the Left=none,none,Switch to Screen to the Left -Switch to Screen to the Right=none,none,Switch to Screen to the Right -Toggle Night Color=none,none,Suspend/Resume Night Light -Toggle Window Raise/Lower=none,none,Toggle Window Raise/Lower -Walk Through Windows=Alt+Tab\tMeta+Tab,Alt+Tab\tMeta+Tab,Walk Through Windows -Walk Through Windows (Reverse)=Alt+Shift+Tab\tMeta+Shift+Tab,Alt+Shift+Tab\tMeta+Shift+Tab,Walk Through Windows (Reverse) -Walk Through Windows Alternative=none,none,Walk Through Windows Alternative -Walk Through Windows Alternative (Reverse)=none,none,Walk Through Windows Alternative (Reverse) -Walk Through Windows of Current Application=Alt+`\tMeta+`,Alt+`\tMeta+`,Walk Through Windows of Current Application -Walk Through Windows of Current Application (Reverse)=Alt+~\tMeta+~,Alt+~\tMeta+~,Walk Through Windows of Current Application (Reverse) -Walk Through Windows of Current Application Alternative=none,none,Walk Through Windows of Current Application Alternative -Walk Through Windows of Current Application Alternative (Reverse)=none,none,Walk Through Windows of Current Application Alternative (Reverse) -Window Above Other Windows=none,none,Keep Window Above Others -Window Below Other Windows=none,none,Keep Window Below Others -Window Close=Meta+Shift+Q,Alt+F4,Close Window -Window Custom Quick Tile Bottom=none,none,Custom Quick Tile Window to the Bottom -Window Custom Quick Tile Left=none,none,Custom Quick Tile Window to the Left -Window Custom Quick Tile Right=none,none,Custom Quick Tile Window to the Right -Window Custom Quick Tile Top=none,none,Custom Quick Tile Window to the Top -Window Fullscreen=none,none,Make Window Fullscreen -Window Grow Horizontal=none,none,Expand Window Horizontally -Window Grow Vertical=none,none,Expand Window Vertically -Window Lower=none,none,Lower Window -Window Maximize=Meta+PgUp,Meta+PgUp,Maximise Window -Window Maximize Horizontal=none,none,Maximise Window Horizontally -Window Maximize Vertical=none,none,Maximise Window Vertically -Window Minimize=Meta+PgDown,Meta+PgDown,Minimise Window -Window Move=none,none,Move Window -Window Move Center=none,none,Move Window to the Centre -Window No Border=none,none,Toggle Window Titlebar and Frame -Window On All Desktops=none,none,Keep Window on All Desktops -Window One Desktop Down=Meta+Ctrl+Shift+Down,Meta+Ctrl+Shift+Down,Window One Desktop Down -Window One Desktop Up=Meta+Ctrl+Shift+Up,Meta+Ctrl+Shift+Up,Window One Desktop Up -Window One Desktop to the Left=Meta+Ctrl+Shift+Left,Meta+Ctrl+Shift+Left,Window One Desktop to the Left -Window One Desktop to the Right=Meta+Ctrl+Shift+Right,Meta+Ctrl+Shift+Right,Window One Desktop to the Right -Window One Screen Down=none,none,Move Window One Screen Down -Window One Screen Up=none,none,Move Window One Screen Up -Window One Screen to the Left=none,none,Move Window One Screen to the Left -Window One Screen to the Right=none,none,Move Window One Screen to the Right -Window Operations Menu=Alt+F3,Alt+F3,Window Menu -Window Pack Down=none,none,Move Window Down -Window Pack Left=none,none,Move Window Left -Window Pack Right=none,none,Move Window Right -Window Pack Up=none,none,Move Window Up -Window Quick Tile Bottom=Meta+Down,Meta+Down,Quick Tile Window to the Bottom -Window Quick Tile Bottom Left=none,none,Quick Tile Window to the Bottom Left -Window Quick Tile Bottom Right=none,none,Quick Tile Window to the Bottom Right -Window Quick Tile Left=Meta+Left,Meta+Left,Quick Tile Window to the Left -Window Quick Tile Right=Meta+Right,Meta+Right,Quick Tile Window to the Right -Window Quick Tile Top=Meta+Up,Meta+Up,Quick Tile Window to the Top -Window Quick Tile Top Left=none,none,Quick Tile Window to the Top Left -Window Quick Tile Top Right=none,none,Quick Tile Window to the Top Right -Window Raise=none,none,Raise Window -Window Resize=none,none,Resize Window -Window Restore=Meta+Backspace,Meta+Backspace,Restore Window -Window Shrink Horizontal=none,none,Shrink Window Horizontally -Window Shrink Vertical=none,none,Shrink Window Vertically -Window to Desktop 1=Meta+!,none,Window to Desktop 1 -Window to Desktop 10=none,none,Window to Desktop 10 -Window to Desktop 11=none,none,Window to Desktop 11 -Window to Desktop 12=none,none,Window to Desktop 12 -Window to Desktop 13=none,none,Window to Desktop 13 -Window to Desktop 14=none,none,Window to Desktop 14 -Window to Desktop 15=none,none,Window to Desktop 15 -Window to Desktop 16=none,none,Window to Desktop 16 -Window to Desktop 17=none,none,Window to Desktop 17 -Window to Desktop 18=none,none,Window to Desktop 18 -Window to Desktop 19=none,none,Window to Desktop 19 -Window to Desktop 2=Meta+@,none,Window to Desktop 2 -Window to Desktop 20=none,none,Window to Desktop 20 -Window to Desktop 21=none,none,Window to Desktop 21 -Window to Desktop 22=none,none,Window to Desktop 22 -Window to Desktop 23=none,none,Window to Desktop 23 -Window to Desktop 24=none,none,Window to Desktop 24 -Window to Desktop 25=none,none,Window to Desktop 25 -Window to Desktop 3=Meta+#,none,Window to Desktop 3 -Window to Desktop 4=Meta+$,none,Window to Desktop 4 -Window to Desktop 5=Meta+%,none,Window to Desktop 5 -Window to Desktop 6=Meta+^,none,Window to Desktop 6 -Window to Desktop 7=Meta+&,none,Window to Desktop 7 -Window to Desktop 8=Meta+*,none,Window to Desktop 8 -Window to Desktop 9=Meta+(,none,Window to Desktop 9 -Window to Next Desktop=none,none,Window to Next Desktop -Window to Next Screen=Meta+Shift+Right,Meta+Shift+Right,Move Window to Next Screen -Window to Previous Desktop=none,none,Window to Previous Desktop -Window to Previous Screen=Meta+Shift+Left,Meta+Shift+Left,Move Window to Previous Screen -Window to Screen 0=none,none,Move Window to Screen 0 -Window to Screen 1=none,none,Move Window to Screen 1 -Window to Screen 2=none,none,Move Window to Screen 2 -Window to Screen 3=none,none,Move Window to Screen 3 -Window to Screen 4=none,none,Move Window to Screen 4 -Window to Screen 5=none,none,Move Window to Screen 5 -Window to Screen 6=none,none,Move Window to Screen 6 -Window to Screen 7=none,none,Move Window to Screen 7 -_k_friendly_name=KWin -disableInputCapture=Meta+Shift+Esc,Meta+Shift+Esc,Disable Active Input Capture -view_actual_size=Meta+0,Meta+0,Zoom to Actual Size -view_zoom_in=Meta++\tMeta+=,Meta++\tMeta+=,Zoom In -view_zoom_out=Meta+-,Meta+-,Zoom Out - -[mediacontrol] -_k_friendly_name=Media Controller -mediavolumedown=none,none,Media volume down -mediavolumeup=none,none,Media volume up -nextmedia=Media Next,Media Next,Media playback next -pausemedia=Media Pause,Media Pause,Pause media playback -playmedia=none,none,Play media playback -playpausemedia=Media Play,Media Play,Play/Pause media playback -previousmedia=Media Previous,Media Previous,Media playback previous -seekbackwardmedia=Media Rewind,Media Rewind,Media playback seek backward 5s -seekbackwardmedialong=none,none,Media playback seek backward 30s -seekforwardmedia=Media Fast Forward,Media Fast Forward,Media playback seek forward 5s -seekforwardmedialong=none,none,Media playback seek forward 30s -stopmedia=Media Stop,Media Stop,Stop media playback - -[org_kde_powerdevil] -Decrease Keyboard Brightness=Keyboard Brightness Down,Keyboard Brightness Down,Decrease Keyboard Brightness -Decrease Screen Brightness=Monitor Brightness Down,Monitor Brightness Down,Decrease Screen Brightness -Decrease Screen Brightness Small=Shift+Monitor Brightness Down,Shift+Monitor Brightness Down,Decrease Screen Brightness by 1% -Hibernate=Hibernate,Hibernate,Hibernate -Increase Keyboard Brightness=Keyboard Brightness Up,Keyboard Brightness Up,Increase Keyboard Brightness -Increase Screen Brightness=Monitor Brightness Up,Monitor Brightness Up,Increase Screen Brightness -Increase Screen Brightness Small=Shift+Monitor Brightness Up,Shift+Monitor Brightness Up,Increase Screen Brightness by 1% -PowerDown=Power Down,Power Down,Power Down -PowerOff=Power Off,Power Off,Power Off -Sleep=Sleep,Sleep,Suspend -Toggle Keyboard Backlight=Keyboard Light On/Off,Keyboard Light On/Off,Toggle Keyboard Backlight -Turn Off Screen=none,none,Turn Off Screen -_k_friendly_name=Power Management -powerProfile=Battery\tMeta+B,Battery\tMeta+B,Switch Power Profile - -[plasmashell] -Slideshow Wallpaper Next Image=none,none,Next Wallpaper Image -_k_friendly_name=plasmashell -activate application launcher=Meta\tAlt+F1,Meta\tAlt+F1,Activate Application Launcher -activate task manager entry 1=none,Meta+1,Activate Task Manager Entry 1 -activate task manager entry 10=none,none,Activate Task Manager Entry 10 -activate task manager entry 2=none,Meta+2,Activate Task Manager Entry 2 -activate task manager entry 3=none,Meta+3,Activate Task Manager Entry 3 -activate task manager entry 4=none,Meta+4,Activate Task Manager Entry 4 -activate task manager entry 5=none,Meta+5,Activate Task Manager Entry 5 -activate task manager entry 6=none,Meta+6,Activate Task Manager Entry 6 -activate task manager entry 7=none,Meta+7,Activate Task Manager Entry 7 -activate task manager entry 8=none,Meta+8,Activate Task Manager Entry 8 -activate task manager entry 9=none,Meta+9,Activate Task Manager Entry 9 -clear history=none,none,Clear Notification History -clear-history=none,none,Clear Clipboard History -clipboard_action=Meta+Ctrl+X,Meta+Ctrl+X,Automatic Action Popup Menu -cycle-panels=Meta+Alt+P,Meta+Alt+P,Move keyboard focus between panels -cycleNextAction=none,none,Next History Item -cyclePrevAction=none,none,Previous History Item -edit_clipboard=none,none,Edit Contents… -manage activities=Meta+Q,Meta+Q,Show Activity Switcher -next activity=Meta+A,none,Walk through activities -previous activity=Meta+Shift+A,none,Walk through activities (Reverse) -repeat_action=none,none,Manually Invoke Action on Current Clipboard -show dashboard=Ctrl+F12,Ctrl+F12,Show Desktop -show-barcode=none,none,Show Barcode… -show-on-mouse-pos=Meta+V,Meta+V,Show Clipboard Items at Mouse Position -switch to next activity=none,none,Switch to Next Activity -switch to previous activity=none,none,Switch to Previous Activity -toggle do not disturb=none,none,Toggle do not disturb - -[services][Alacritty.desktop] -_launch=Meta+Return - -[services][net.local.fish.desktop] -_launch=Meta+Ctrl+Space - -[services][org.kde.konsole.desktop] -_launch=none diff --git a/reference/home/.config/konsolerc b/reference/home/.config/konsolerc deleted file mode 100644 index ad9bfb8..0000000 --- a/reference/home/.config/konsolerc +++ /dev/null @@ -1,5 +0,0 @@ -[General] -ConfigVersion=1 - -[UiSettings] -ColorScheme= diff --git a/reference/home/.config/kwinoutputconfig.json b/reference/home/.config/kwinoutputconfig.json deleted file mode 100644 index 98b4a89..0000000 --- a/reference/home/.config/kwinoutputconfig.json +++ /dev/null @@ -1,93 +0,0 @@ -[ - { - "data": [ - { - "abmLevel": 0, - "allowDdcCi": true, - "allowSdrSoftwareBrightness": false, - "autoBrightnessCurve": [ - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0 - ], - "autoRotation": "InTabletMode", - "automaticBrightness": false, - "brightness": 1, - "colorPowerTradeoff": "PreferEfficiency", - "colorProfileSource": "sRGB", - "connectorName": "eDP-1", - "customModes": [ - ], - "detectedDdcCi": false, - "edidHash": "5f3fe94f152da0c618ae754a160d08df", - "edidIdentifier": "AUO 23341 0 0 2018 0", - "edrPolicy": "always", - "hdrColorProfileSource": "EDID", - "hdrIccProfilePath": "", - "highDynamicRange": false, - "iccProfilePath": "", - "maxBitsPerColor": 0, - "mode": { - "flags": 1, - "height": 1080, - "refreshRate": 60033, - "width": 1920 - }, - "overscan": 0, - "rgbRange": "Automatic", - "scale": 1, - "sdrBrightness": 200, - "sdrGamutWideness": 0, - "sharpness": 0, - "transform": "Normal", - "uuid": "3f5a91e5-1c16-44d8-a5f5-b5854daa57be", - "vrrPolicy": "Never", - "wideColorGamut": false - } - ], - "name": "outputs" - }, - { - "data": [ - { - "lidClosed": false, - "outputs": [ - { - "enabled": true, - "outputIndex": 0, - "position": { - "x": 0, - "y": 0 - }, - "priority": 1, - "replicationSource": "" - } - ] - }, - { - "lidClosed": true, - "outputs": [ - { - "enabled": true, - "outputIndex": 0, - "position": { - "x": 0, - "y": 0 - }, - "priority": 0, - "replicationSource": "" - } - ] - } - ], - "name": "setups" - } -] diff --git a/reference/home/.config/kwinrc b/reference/home/.config/kwinrc deleted file mode 100644 index 6d0079e..0000000 --- a/reference/home/.config/kwinrc +++ /dev/null @@ -1,60 +0,0 @@ -[Desktops] -Id_1=4c183a08-13bd-40a0-8131-12584ba0b0c0 -Id_2=b20e6cf5-12e6-44c9-b8f1-0bb5a5e2307a -Id_3=77ff2787-e9df-4539-b009-8da2084b3ba0 -Id_4=f4bc9302-9b20-4cbb-b73b-4cfecba52889 -Id_5=e0032b5e-9f48-4219-8800-152a21b01604 -Id_6=a3ac962a-66e0-4cfe-a9ca-a789eae23a3a -Id_7=6ecab170-20d2-4383-b152-0d443d77b8ad -Id_8=ba6489cf-4f95-4786-af9f-3d742e17e6c5 -Id_9=5f9b8e88-3740-4d2c-9ac5-e76e072606f4 -Number=9 -Rows=1 - -[Effect-zoom] -InitialZoom=4.136880223300198 - -[Plugins] -zoomEnabled=false - -[Tiling][4c183a08-13bd-40a0-8131-12584ba0b0c0][3f5a91e5-1c16-44d8-a5f5-b5854daa57be] -padding=4 -tiles={"layoutDirection":"horizontal","tiles":[{"width":0.25},{"width":0.5},{"width":0.25}]} - -[Tiling][5f9b8e88-3740-4d2c-9ac5-e76e072606f4][3f5a91e5-1c16-44d8-a5f5-b5854daa57be] -padding=4 -tiles={"layoutDirection":"horizontal","tiles":[{"width":0.25},{"width":0.5},{"width":0.25}]} - -[Tiling][6ecab170-20d2-4383-b152-0d443d77b8ad][3f5a91e5-1c16-44d8-a5f5-b5854daa57be] -padding=4 -tiles={"layoutDirection":"horizontal","tiles":[{"width":0.25},{"width":0.5},{"width":0.25}]} - -[Tiling][77ff2787-e9df-4539-b009-8da2084b3ba0][3f5a91e5-1c16-44d8-a5f5-b5854daa57be] -padding=4 -tiles={"layoutDirection":"horizontal","tiles":[{"width":0.25},{"width":0.5},{"width":0.25}]} - -[Tiling][a3ac962a-66e0-4cfe-a9ca-a789eae23a3a][3f5a91e5-1c16-44d8-a5f5-b5854daa57be] -padding=4 -tiles={"layoutDirection":"horizontal","tiles":[{"width":0.25},{"width":0.5},{"width":0.25}]} - -[Tiling][b20e6cf5-12e6-44c9-b8f1-0bb5a5e2307a][3f5a91e5-1c16-44d8-a5f5-b5854daa57be] -padding=4 -tiles={"layoutDirection":"horizontal","tiles":[{"width":0.25},{"width":0.5},{"width":0.25}]} - -[Tiling][ba6489cf-4f95-4786-af9f-3d742e17e6c5][3f5a91e5-1c16-44d8-a5f5-b5854daa57be] -padding=4 -tiles={"layoutDirection":"horizontal","tiles":[{"width":0.25},{"width":0.5},{"width":0.25}]} - -[Tiling][e0032b5e-9f48-4219-8800-152a21b01604][3f5a91e5-1c16-44d8-a5f5-b5854daa57be] -padding=4 -tiles={"layoutDirection":"horizontal","tiles":[{"width":0.25},{"width":0.5},{"width":0.25}]} - -[Tiling][f4bc9302-9b20-4cbb-b73b-4cfecba52889][3f5a91e5-1c16-44d8-a5f5-b5854daa57be] -padding=4 -tiles={"layoutDirection":"horizontal","tiles":[{"width":0.25},{"width":0.5},{"width":0.25}]} - -[Windows] -RollOverDesktops=true - -[Xwayland] -Scale=1 diff --git a/reference/home/.config/kxkbrc b/reference/home/.config/kxkbrc deleted file mode 100644 index 4ba34e1..0000000 --- a/reference/home/.config/kxkbrc +++ /dev/null @@ -1,3 +0,0 @@ -[Layout] -Options=caps:escape_shifted_capslock -ResetOldOptions=true diff --git a/reference/home/.config/micro/colorschemes/catppuccin-frappe.micro b/reference/home/.config/micro/colorschemes/catppuccin-frappe.micro deleted file mode 100644 index 6bdff93..0000000 --- a/reference/home/.config/micro/colorschemes/catppuccin-frappe.micro +++ /dev/null @@ -1,42 +0,0 @@ -color-link default "#C6D0F5,#303446" -color-link comment "#626880" - -color-link identifier "#8CAAEE" -color-link identifier.class "#8CAAEE" -color-link identifier.var "#8CAAEE" - -color-link constant "#EF9F76" -color-link constant.number "#EF9F76" -color-link constant.string "#A6D189" - -color-link symbol "#F4B8E4" -color-link symbol.brackets "#EEBEBE" -color-link symbol.tag "#8CAAEE" - -color-link type "#8CAAEE" -color-link type.keyword "#E5C890" - -color-link special "#F4B8E4" -color-link statement "#CA9EE6" -color-link preproc "#F4B8E4" - -color-link underlined "#99D1DB" -color-link error "bold #E78284" -color-link todo "bold #E5C890" - -color-link diff-added "#A6D189" -color-link diff-modified "#E5C890" -color-link diff-deleted "#E78284" - -color-link gutter-error "#E78284" -color-link gutter-warning "#E5C890" - -color-link statusline "#F2D5CF,#292C3C" -color-link tabbar "#F2D5CF,#292C3C" -color-link indent-char "#51576D" -color-link line-number "#51576D" -color-link current-line-number "#BABBF1" - -color-link cursor-line "#414559,#C6D0F5" -color-link color-column "#414559" -color-link type.extended "default" diff --git a/reference/home/.config/micro/colorschemes/catppuccin-latte.micro b/reference/home/.config/micro/colorschemes/catppuccin-latte.micro deleted file mode 100644 index 80b3727..0000000 --- a/reference/home/.config/micro/colorschemes/catppuccin-latte.micro +++ /dev/null @@ -1,42 +0,0 @@ -color-link default "#4C4F69,#EFF1F5" -color-link comment "#ACB0BE" - -color-link identifier "#1E66F5" -color-link identifier.class "#1E66F5" -color-link identifier.var "#1E66F5" - -color-link constant "#FE640B" -color-link constant.number "#FE640B" -color-link constant.string "#40A02B" - -color-link symbol "#EA76CB" -color-link symbol.brackets "#DD7878" -color-link symbol.tag "#1E66F5" - -color-link type "#1E66F5" -color-link type.keyword "#DF8E1D" - -color-link special "#EA76CB" -color-link statement "#8839EF" -color-link preproc "#EA76CB" - -color-link underlined "#04A5E5" -color-link error "bold #D20F39" -color-link todo "bold #DF8E1D" - -color-link diff-added "#40A02B" -color-link diff-modified "#DF8E1D" -color-link diff-deleted "#D20F39" - -color-link gutter-error "#D20F39" -color-link gutter-warning "#DF8E1D" - -color-link statusline "#4C4F69,#DCE0E8" -color-link tabbar "#4C4F69,#DCE0E8" -color-link indent-char "#BCC0CC" -color-link line-number "#BCC0CC" -color-link current-line-number "#7287FD" - -color-link cursor-line "#CCD0DA,#4C4F69" -color-link color-column "#CCD0DA" -color-link type.extended "default" diff --git a/reference/home/.config/micro/colorschemes/catppuccin-macchiato.micro b/reference/home/.config/micro/colorschemes/catppuccin-macchiato.micro deleted file mode 100644 index 8634683..0000000 --- a/reference/home/.config/micro/colorschemes/catppuccin-macchiato.micro +++ /dev/null @@ -1,42 +0,0 @@ -color-link default "#CAD3F5,#24273A" -color-link comment "#5B6078" - -color-link identifier "#8AADF4" -color-link identifier.class "#8AADF4" -color-link identifier.var "#8AADF4" - -color-link constant "#F5A97F" -color-link constant.number "#F5A97F" -color-link constant.string "#A6DA95" - -color-link symbol "#F5BDE6" -color-link symbol.brackets "#F0C6C6" -color-link symbol.tag "#8AADF4" - -color-link type "#8AADF4" -color-link type.keyword "#EED49F" - -color-link special "#F5BDE6" -color-link statement "#C6A0F6" -color-link preproc "#F5BDE6" - -color-link underlined "#8AADF4" -color-link error "bold #ED8796" -color-link todo "bold #EED49F" - -color-link diff-added "#A6DA95" -color-link diff-modified "#EED49F" -color-link diff-deleted "#ED8796" - -color-link gutter-error "#ED8796" -color-link gutter-warning "#EED49F" - -color-link statusline "#F4DBD6,#1E2030" -color-link tabbar "#F4DBD6,#1E2030" -color-link indent-char "#494D64" -color-link line-number "#494D64" -color-link current-line-number "#B7BDF8" - -color-link cursor-line "#363A4F,#CAD3F5" -color-link color-column "#363A4F" -color-link type.extended "default" diff --git a/reference/home/.config/micro/colorschemes/catppuccin-mocha.micro b/reference/home/.config/micro/colorschemes/catppuccin-mocha.micro deleted file mode 100644 index d00718c..0000000 --- a/reference/home/.config/micro/colorschemes/catppuccin-mocha.micro +++ /dev/null @@ -1,42 +0,0 @@ -color-link default "#C6D0F5,#1E1E2E" -color-link comment "#585B70" - -color-link identifier "#89B4FA" -color-link identifier.class "#89B4FA" -color-link identifier.var "#89B4FA" - -color-link constant "#FAB387" -color-link constant.number "#FAB387" -color-link constant.string "#A6E3A1" - -color-link symbol "#F5C2E7" -color-link symbol.brackets "#F2CDCD" -color-link symbol.tag "#89B4FA" - -color-link type "#89B4FA" -color-link type.keyword "#F9E2AF" - -color-link special "#F5C2E7" -color-link statement "#CBA6F7" -color-link preproc "#F5C2E7" - -color-link underlined "#89DCEB" -color-link error "bold #F38BA8" -color-link todo "bold #F9E2AF" - -color-link diff-added "#A6E3A1" -color-link diff-modified "#F9E2AF" -color-link diff-deleted "#F38BA8" - -color-link gutter-error "#F38BA8" -color-link gutter-warning "#F9E2AF" - -color-link statusline "#F5E0DC,#181825" -color-link tabbar "#F5E0DC,#181825" -color-link indent-char "#45475A" -color-link line-number "#45475A" -color-link current-line-number "#B4BEFE" - -color-link cursor-line "#313244,#C6D0F5" -color-link color-column "#313244" -color-link type.extended "default" diff --git a/reference/home/.config/micro/settings.json b/reference/home/.config/micro/settings.json deleted file mode 100644 index eef4c52..0000000 --- a/reference/home/.config/micro/settings.json +++ /dev/null @@ -1,3 +0,0 @@ -{ - "colorscheme": "catppuccin-macchiato" -} diff --git a/reference/home/.config/mimeapps.list b/reference/home/.config/mimeapps.list deleted file mode 100644 index 03da0ea..0000000 --- a/reference/home/.config/mimeapps.list +++ /dev/null @@ -1,4 +0,0 @@ - -[Default Applications] -x-scheme-handler/claude-cli=claude-code-url-handler.desktop -x-scheme-handler/openwhispr=openwhispr.desktop diff --git a/reference/home/.config/nvim/after/ftplugin/markdown.lua b/reference/home/.config/nvim/after/ftplugin/markdown.lua deleted file mode 100644 index 2848685..0000000 --- a/reference/home/.config/nvim/after/ftplugin/markdown.lua +++ /dev/null @@ -1 +0,0 @@ -vim.opt_local.conceallevel = 2 diff --git a/reference/home/.config/nvim/init.lua b/reference/home/.config/nvim/init.lua deleted file mode 100644 index 5c7f4ef..0000000 --- a/reference/home/.config/nvim/init.lua +++ /dev/null @@ -1,3 +0,0 @@ -require("vim_options") -require("keys") -require("plugin") diff --git a/reference/home/.config/nvim/lazy-lock.json b/reference/home/.config/nvim/lazy-lock.json deleted file mode 100644 index 2ec59b6..0000000 --- a/reference/home/.config/nvim/lazy-lock.json +++ /dev/null @@ -1,13 +0,0 @@ -{ - "diffview.nvim": { "branch": "main", "commit": "4516612fe98ff56ae0415a259ff6361a89419b0a" }, - "gitsigns.nvim": { "branch": "main", "commit": "eb60cc7b94c46005237fd34170d76f3a089a90aa" }, - "lazy.nvim": { "branch": "main", "commit": "306a05526ada86a7b30af95c5cc81ffba93fef97" }, - "neogit": { "branch": "master", "commit": "6fc2fa890bd2031ed999c074daab0fb4feff20a5" }, - "nord.nvim": { "branch": "main", "commit": "87394d4fc35c901bbe38326a78d31ab1ead826b6" }, - "nvim-treesitter": { "branch": "master", "commit": "cf12346a3414fa1b06af75c79faebe7f76df080a" }, - "oil.nvim": { "branch": "master", "commit": "b73018b75affd13fa38e2fc94ef753b465f770d7" }, - "plenary.nvim": { "branch": "master", "commit": "74b06c6c75e4eeb3108ec01852001636d85a932b" }, - "render-markdown.nvim": { "branch": "main", "commit": "f422cb5c6855f150e2ddcfaf44e7157b98b34f6a" }, - "snacks.nvim": { "branch": "main", "commit": "882c996cf28183f4d63640de0b4c02ec886d01f2" }, - "which-key.nvim": { "branch": "main", "commit": "3aab2147e74890957785941f0c1ad87d0a44c15a" } -} diff --git a/reference/home/.config/nvim/lua/keys.lua b/reference/home/.config/nvim/lua/keys.lua deleted file mode 100644 index 486b5da..0000000 --- a/reference/home/.config/nvim/lua/keys.lua +++ /dev/null @@ -1,8 +0,0 @@ -local map = vim.keymap.set - -map("n", "", "h", { desc = "Move focus left" }) -map("n", "", "j", { desc = "Move focus down" }) -map("n", "", "k", { desc = "Move focus up" }) -map("n", "", "l", { desc = "Move focus right" }) - -map("n", "", "nohlsearch", { desc = "Clear search highlight" }) diff --git a/reference/home/.config/nvim/lua/plugin.lua b/reference/home/.config/nvim/lua/plugin.lua deleted file mode 100644 index d2eea07..0000000 --- a/reference/home/.config/nvim/lua/plugin.lua +++ /dev/null @@ -1,23 +0,0 @@ -local lazypath = vim.fn.stdpath("data") .. "/lazy/lazy.nvim" -if not vim.uv.fs_stat(lazypath) then - local lazyrepo = "https://github.com/folke/lazy.nvim.git" - local out = vim.fn.system({ "git", "clone", "--filter=blob:none", "--branch=stable", lazyrepo, lazypath }) - if vim.v.shell_error ~= 0 then - vim.api.nvim_echo({ - { "Failed to clone lazy.nvim:\n", "ErrorMsg" }, - { out, "WarningMsg" }, - { "\nPress any key to exit..." }, - }, true, {}) - vim.fn.getchar() - os.exit(1) - end -end -vim.opt.rtp:prepend(lazypath) - -require("lazy").setup({ - spec = { - { import = "plugins" }, - }, - install = { colorscheme = { "nord" } }, - checker = { enabled = false }, -}) diff --git a/reference/home/.config/nvim/lua/plugins/git.lua b/reference/home/.config/nvim/lua/plugins/git.lua deleted file mode 100644 index 94d7889..0000000 --- a/reference/home/.config/nvim/lua/plugins/git.lua +++ /dev/null @@ -1,36 +0,0 @@ -return { - { - "NeogitOrg/neogit", - dependencies = { - "nvim-lua/plenary.nvim", - "sindrets/diffview.nvim", - }, - keys = { - { - "g", - function() - require("gitsigns").toggle_current_line_blame(true) - require("neogit").open() - end, - desc = "Open git (Neogit)", - }, - }, - config = function() - require("neogit").setup() - vim.api.nvim_create_autocmd("BufUnload", { - callback = function(args) - if vim.bo[args.buf].filetype == "NeogitStatus" then - require("gitsigns").toggle_current_line_blame(false) - end - end, - }) - end, - }, - { - "lewis6991/gitsigns.nvim", - event = "BufWinEnter", - opts = { - current_line_blame = false, - }, - }, -} diff --git a/reference/home/.config/nvim/lua/plugins/navigation.lua b/reference/home/.config/nvim/lua/plugins/navigation.lua deleted file mode 100644 index efa86f9..0000000 --- a/reference/home/.config/nvim/lua/plugins/navigation.lua +++ /dev/null @@ -1,27 +0,0 @@ -return { - { - "stevearc/oil.nvim", - lazy = false, - opts = { - view_options = { show_hidden = true }, - }, - keys = { - { "e", "Oil", desc = "Open file browser" }, - }, - }, - { - "folke/snacks.nvim", - priority = 1000, - lazy = false, - opts = { - picker = { enabled = true }, - notifier = { enabled = true }, - input = { enabled = true }, - }, - keys = { - { "f", function() require("snacks").picker.files() end, desc = "Find files" }, - { "s", function() require("snacks").picker.grep() end, desc = "Search text" }, - { "b", function() require("snacks").picker.buffers() end, desc = "Switch buffer" }, - }, - }, -} diff --git a/reference/home/.config/nvim/lua/plugins/ui.lua b/reference/home/.config/nvim/lua/plugins/ui.lua deleted file mode 100644 index 5127f0a..0000000 --- a/reference/home/.config/nvim/lua/plugins/ui.lua +++ /dev/null @@ -1,55 +0,0 @@ -return { - { - "gbprod/nord.nvim", - lazy = false, - priority = 1000, - opts = { - transparent = true, - }, - config = function(_, opts) - require("nord").setup(opts) - vim.cmd.colorscheme("nord") - end, - }, - { - "MeanderingProgrammer/render-markdown.nvim", - ft = { "markdown" }, - dependencies = { "nvim-treesitter/nvim-treesitter" }, - opts = {}, - }, - { - "folke/which-key.nvim", - lazy = false, - config = true, - }, - { - "nvim-treesitter/nvim-treesitter", - branch = "master", - build = ":TSUpdate", - opts = { - ensure_installed = { - "markdown", - "markdown_inline", - "lua", - "bash", - "fish", - "rust", - "javascript", - "typescript", - "java", - "kotlin", - "c", - "cpp", - "html", - "css", - "python", - }, - auto_install = false, - highlight = { enable = true }, - indent = { enable = true }, - }, - config = function(_, opts) - require("nvim-treesitter.configs").setup(opts) - end, - }, -} diff --git a/reference/home/.config/nvim/lua/vim_options.lua b/reference/home/.config/nvim/lua/vim_options.lua deleted file mode 100644 index f8ee951..0000000 --- a/reference/home/.config/nvim/lua/vim_options.lua +++ /dev/null @@ -1,29 +0,0 @@ -vim.g.mapleader = " " - -local opt = vim.opt - --- Clipboard: use neovim's built-in OSC 52 provider, no external binary needed. -vim.g.clipboard = "osc52" -opt.clipboard = "unnamedplus" - -opt.number = true -opt.relativenumber = true - -opt.shiftwidth = 2 -opt.tabstop = 2 -opt.expandtab = true - -opt.mouse = "a" - -opt.undofile = true - -opt.ignorecase = true -opt.smartcase = true - -opt.splitright = true -opt.splitbelow = true - -opt.wrap = false - -opt.scrolloff = 8 -opt.cursorline = true diff --git a/reference/home/.config/plasma-org.kde.plasma.desktop-appletsrc b/reference/home/.config/plasma-org.kde.plasma.desktop-appletsrc deleted file mode 100644 index 700fe24..0000000 --- a/reference/home/.config/plasma-org.kde.plasma.desktop-appletsrc +++ /dev/null @@ -1,173 +0,0 @@ -[ActionPlugins][0] -MiddleButton;NoModifier=org.kde.paste -RightButton;NoModifier=org.kde.contextmenu - -[ActionPlugins][1] -RightButton;NoModifier=org.kde.contextmenu - -[Containments][23] -activityId= -formfactor=2 -immutability=1 -lastScreen=0 -location=4 -plugin=org.kde.panel -wallpaperplugin=org.kde.image - -[Containments][23][Applets][24] -immutability=1 -plugin=org.kde.plasma.kickoff - -[Containments][23][Applets][24][Configuration] -popupHeight=593 -popupWidth=789 - -[Containments][23][Applets][24][Configuration][ConfigDialog] -DialogHeight=540 -DialogWidth=720 - -[Containments][23][Applets][24][Configuration][General] -favoritesPortedToKAstats=true -icon=org.cachyos.hello -systemFavorites=suspend\\,hibernate\\,reboot\\,shutdown - -[Containments][23][Applets][24][Configuration][Shortcuts] -global=Alt+F1 - -[Containments][23][Applets][25] -immutability=1 -plugin=org.kde.plasma.pager - -[Containments][23][Applets][26] -immutability=1 -plugin=org.kde.plasma.icontasks - -[Containments][23][Applets][26][Configuration][ConfigDialog] -DialogHeight=630 -DialogWidth=810 - -[Containments][23][Applets][26][Configuration][General] -launchers= - -[Containments][23][Applets][27] -immutability=1 -plugin=org.kde.plasma.marginsseparator - -[Containments][23][Applets][28] -activityId= -formfactor=2 -immutability=1 -lastScreen=0 -location=4 -plugin=org.kde.plasma.systemtray -popupHeight=432 -popupWidth=432 -wallpaperplugin=org.kde.image - -[Containments][23][Applets][28][Applets][29] -immutability=1 -plugin=org.kde.kdeconnect - -[Containments][23][Applets][28][Applets][30] -immutability=1 -plugin=org.kde.kscreen - -[Containments][23][Applets][28][Applets][31] -immutability=1 -plugin=org.kde.plasma.cameraindicator - -[Containments][23][Applets][28][Applets][32] -immutability=1 -plugin=org.kde.plasma.clipboard - -[Containments][23][Applets][28][Applets][33] -immutability=1 -plugin=org.kde.plasma.devicenotifier - -[Containments][23][Applets][28][Applets][34] -immutability=1 -plugin=org.kde.plasma.keyboardlayout - -[Containments][23][Applets][28][Applets][35] -immutability=1 -plugin=org.kde.plasma.manage-inputmethod - -[Containments][23][Applets][28][Applets][36] -immutability=1 -plugin=org.kde.plasma.notifications - -[Containments][23][Applets][28][Applets][37] -immutability=1 -plugin=org.kde.plasma.keyboardindicator - -[Containments][23][Applets][28][Applets][37][Configuration][General] -migrated=true - -[Containments][23][Applets][28][Applets][38] -immutability=1 -plugin=org.kde.plasma.weather - -[Containments][23][Applets][28][Applets][40] -immutability=1 -plugin=org.kde.plasma.battery - -[Containments][23][Applets][28][Applets][41] -immutability=1 -plugin=org.kde.plasma.brightness - -[Containments][23][Applets][28][Applets][42] -immutability=1 -plugin=org.kde.plasma.networkmanagement - -[Containments][23][Applets][28][Applets][43] -immutability=1 -plugin=org.kde.plasma.volume - -[Containments][23][Applets][28][Applets][43][Configuration][General] -migrated=true - -[Containments][23][Applets][28][Applets][44] -immutability=1 -plugin=org.kde.plasma.bluetooth - -[Containments][23][Applets][28][Applets][47] -immutability=1 -plugin=org.kde.plasma.mediacontroller - -[Containments][23][Applets][28][General] -extraItems=org.kde.kscreen,org.kde.plasma.battery,org.kde.plasma.brightness,org.kde.plasma.cameraindicator,org.kde.plasma.clipboard,org.kde.plasma.devicenotifier,org.kde.plasma.keyboardlayout,org.kde.plasma.manage-inputmethod,org.kde.plasma.mediacontroller,org.kde.plasma.networkmanagement,org.kde.plasma.notifications,org.kde.plasma.volume,org.kde.kdeconnect,org.kde.plasma.bluetooth,org.kde.plasma.keyboardindicator,org.kde.plasma.weather -knownItems=org.kde.kscreen,org.kde.plasma.battery,org.kde.plasma.brightness,org.kde.plasma.cameraindicator,org.kde.plasma.clipboard,org.kde.plasma.devicenotifier,org.kde.plasma.keyboardlayout,org.kde.plasma.manage-inputmethod,org.kde.plasma.mediacontroller,org.kde.plasma.networkmanagement,org.kde.plasma.notifications,org.kde.plasma.volume,org.kde.kdeconnect,org.kde.plasma.bluetooth,org.kde.plasma.keyboardindicator,org.kde.plasma.weather - -[Containments][23][Applets][38] -immutability=1 -plugin=org.kde.plasma.digitalclock - -[Containments][23][Applets][38][Configuration] -popupHeight=400 -popupWidth=560 - -[Containments][23][Applets][39] -immutability=1 -plugin=org.kde.plasma.showdesktop - -[Containments][23][General] -AppletOrder=24;25;26;27;28;38;39 - -[Containments][4][Wallpaper][org.kde.image][General] -Image=/usr/share/wallpapers/cachyos-wallpapers/north.png -PreviewImage=/usr/share/wallpapers/cachyos-wallpapers/north.png -SlidePaths=/usr/share/wallpapers/ - -[Containments][43] -ItemGeometries-1920x1080= -ItemGeometriesHorizontal= -activityId=4dfe7a63-75b1-4532-89bb-9e8ebc7e360d -formfactor=0 -immutability=1 -lastScreen=0 -location=0 -plugin=org.kde.plasma.folder -wallpaperplugin=org.kde.image - -[ScreenMapping] -itemsOnDisabledScreens= diff --git a/reference/home/.config/powermanagementprofilesrc b/reference/home/.config/powermanagementprofilesrc deleted file mode 100644 index 0f36073..0000000 --- a/reference/home/.config/powermanagementprofilesrc +++ /dev/null @@ -1,2 +0,0 @@ -[Migration] -MigratedProfilesToPlasma6=powerdevilrc diff --git a/reference/home/.config/shelly/config.json b/reference/home/.config/shelly/config.json deleted file mode 100644 index b45ea16..0000000 --- a/reference/home/.config/shelly/config.json +++ /dev/null @@ -1,56 +0,0 @@ -{ - "FileSizeDisplay": "Megabytes", - "DefaultExecution": "UpgradeAll", - "ParallelDownloadCount": 10, - "Culture": "", - "AurEnabled": true, - "ShellySearchEnabled": false, - "AurWarningConfirmed": true, - "FlatPackEnabled": true, - "WindowWidth": 1021, - "WindowHeight": 600, - "DefaultView": "HomeScreen", - "UseOldMenu": false, - "TrayEnabled": true, - "TrayCheckIntervalHours": 72, - "NoConfirm": false, - "NewInstall": false, - "CurrentVersion": "2.4.1.2", - "UseWeeklySchedule": false, - "DaysOfWeek": [], - "Time": null, - "ShellyIconsEnabled": true, - "AppImageEnabled": true, - "NewInstallInitSettings": true, - "UseSymbolicTray": true, - "RemoveCache": false, - "TrayIconPath": "", - "TrayUpdatesIconPath": "", - "DefaultPageDropDown": "Packages", - "RecommendedEnabled": true, - "ProgressBarStyle": "Blocks", - "ProgressBarFps": 7, - "ProgressBarWidth": 24, - "OutputMode": "singlepane", - "SinglePaneMaxStickies": 6, - "TrayAutoStart": false, - "PackageDowngradeEnabled": false, - "PackageManagementCascadeDelete": true, - "PackageManagementRemoveConfigs": false, - "PackageManagementRemoveOptionalDeps": true, - "PackageManagementShowHidden": false, - "PackageInstallUpgrade": false, - "PackageInstallShowHidden": false, - "PackageUpdateShowHidden": false, - "AurInstallUseChroot": false, - "AurInstallRunChecks": false, - "AurRemoveCascadeDelete": true, - "AurRemoveShowHidden": false, - "AurUpdateRunChecks": false, - "AurUpdateShowHidden": false, - "AppImageInstallPath": "", - "StarFishEnabled": false, - "PackageInstallView": "Grid", - "PackageUpdateView": "Grid", - "PackageManageView": "Grid" -} \ No newline at end of file diff --git a/reference/home/.config/spectaclerc b/reference/home/.config/spectaclerc deleted file mode 100644 index 364a9e3..0000000 --- a/reference/home/.config/spectaclerc +++ /dev/null @@ -1,6 +0,0 @@ -[ImageSave] -lastImageSaveLocation=file:///home/alexion/Pictures/Screenshots/Screenshot_20260704_153951.png -translatedScreenshotsFolder=Screenshots - -[VideoSave] -translatedScreencastsFolder=Screencasts diff --git a/reference/home/.config/tmux/tmux.conf b/reference/home/.config/tmux/tmux.conf deleted file mode 100644 index c478c78..0000000 --- a/reference/home/.config/tmux/tmux.conf +++ /dev/null @@ -1,74 +0,0 @@ -# Prefix: Ctrl-Space. Chosen over Ctrl-b (awkward reach) and Ctrl-a (collides -# with readline's beginning-of-line, which fights editing text in shells and -# in Claude Code's prompt). Verified clear of IME/KDE/Claude Code bindings. -unbind C-b -set -g prefix C-Space -bind C-Space send-prefix - -set -g mouse on -# OSC52 lets copy-mode selections land in the system clipboard via the -# terminal itself (Alacritty supports it) -- no wl-copy/xclip needed, and it -# still works over SSH later since the escape sequence travels with the data. -set -g set-clipboard on - -set -g mode-keys vi -set -g status-keys vi -bind -T copy-mode-vi v send -X begin-selection -bind -T copy-mode-vi y send -X copy-selection-and-cancel -bind -T copy-mode-vi MouseDragEnd1Pane send -X copy-selection-and-cancel - -# tmux's -h/-v split flags name the *arrangement*, not the divider line, which -# is backwards from how the divider looks -- so pick keys by what they draw: -# \ draws a side-by-side split (vertical line), - draws a stacked split -# (horizontal line). Unshifted versions of |/- since splitting is frequent. -unbind % -unbind '"' -bind \\ split-window -h -c "#{pane_current_path}" -bind - split-window -v -c "#{pane_current_path}" -bind c new-window -c "#{pane_current_path}" - -bind h select-pane -L -bind j select-pane -D -bind k select-pane -U -bind l select-pane -R - -set -g base-index 1 -setw -g pane-base-index 1 -set -g renumber-windows on - -bind r source-file ~/.config/tmux/tmux.conf \; display-message "tmux.conf reloaded" - -# True color passthrough. ",*" (rather than naming Alacritty's xterm-256color -# specifically) so this keeps working if the terminal emulator changes later. -set -g default-terminal "tmux-256color" -set -ag terminal-overrides ",*:RGB" - -# Default 500ms delay on Esc exists to disambiguate meta-key sequences; it -# reads as noticeable lag exiting insert mode in neovim, so drop it. -set -sg escape-time 10 - -set -g history-limit 10000 - -# Flag a background window in the status bar when its Claude Code session -# rings the terminal bell (permission prompt / task done while unfocused). -# bell-action=none stops tmux from ever passing the actual BEL through to -# Alacritty (no beep, no flash) -- monitor-bell's per-window tracking for the -# status-line highlight is independent of that and keeps working. -setw -g monitor-bell on -set -g bell-action none - -# Minimal status bar (session + window list only), styled to match the Nord -# theme already used in alacritty.toml. -set -g status-position bottom -set -g status-style "bg=#2E3440,fg=#D8DEE9" -set -g status-left " #S " -set -g status-left-length 20 -set -g status-right "" -setw -g window-status-current-style "bg=#88C0D0,fg=#2E3440,bold" -setw -g window-status-current-format " #I:#W " -setw -g window-status-format " #I:#W " -setw -g window-status-style "fg=#4C566A" -setw -g window-status-bell-style "bg=#BF616A,fg=#2E3440,bold" - -set -g pane-border-style "fg=#3B4252" -set -g pane-active-border-style "fg=#88C0D0" diff --git a/reference/home/.config/user-dirs.dirs b/reference/home/.config/user-dirs.dirs deleted file mode 100644 index 1b5a3ff..0000000 --- a/reference/home/.config/user-dirs.dirs +++ /dev/null @@ -1,16 +0,0 @@ -# This file is written by xdg-user-dirs-update -# If you want to change or add directories, just edit the line you're -# interested in. All local changes will be retained on the next run. -# Format is XDG_xxx_DIR="$HOME/yyy", where yyy is a shell-escaped -# homedir-relative path, or XDG_xxx_DIR="/yyy", where /yyy is an -# absolute path. No other format is supported. -# -XDG_DESKTOP_DIR="$HOME/.desktop" -XDG_DOWNLOAD_DIR="$HOME/dwn" -XDG_TEMPLATES_DIR="$HOME/.ignoreme" -XDG_PUBLICSHARE_DIR="$HOME/.ignoreme" -XDG_DOCUMENTS_DIR="$HOME/doc" -XDG_MUSIC_DIR="$HOME/mus" -XDG_PICTURES_DIR="$HOME/pic" -XDG_VIDEOS_DIR="$HOME/vid" -XDG_PROJECTS_DIR="$HOME/wrk" diff --git a/reference/home/.config/xsettingsd/xsettingsd.conf b/reference/home/.config/xsettingsd/xsettingsd.conf deleted file mode 100644 index 6f1b3fd..0000000 --- a/reference/home/.config/xsettingsd/xsettingsd.conf +++ /dev/null @@ -1,17 +0,0 @@ -Net/CursorBlinkTime 1000 -Net/CursorBlink 1 -Gdk/UnscaledDPI 98304 -Gdk/WindowScalingFactor 1 -Gtk/EnableAnimations 1 -Gtk/DecorationLayout "icon:minimize,maximize,close" -Net/ThemeName "Breeze-Dark" -Gtk/PrimaryButtonWarpsSlider 1 -Gtk/ToolbarStyle 3 -Gtk/MenuImages 1 -Gtk/ButtonImages 1 -Gtk/CursorThemeSize 24 -Gtk/CursorThemeName "breeze_cursors" -Net/SoundThemeName "ocean" -Net/IconThemeName "breeze-dark" -Gtk/FontName "Noto Sans, 14" - diff --git a/reference/home/.gitconfig b/reference/home/.gitconfig deleted file mode 100644 index 0e25d32..0000000 --- a/reference/home/.gitconfig +++ /dev/null @@ -1,3 +0,0 @@ -[user] - name = alexion - email = contact@alexion.dev diff --git a/reference/home/.gitignore b/reference/home/.gitignore deleted file mode 100644 index d5cbde1..0000000 --- a/reference/home/.gitignore +++ /dev/null @@ -1,8 +0,0 @@ -.dotfiles -.DS_Store -*.swp -*.swo -*~ -Thumbs.db -**/__pycache__ -.config/fish/conf.d/secrets.fish diff --git a/reference/home/.gtkrc-2.0 b/reference/home/.gtkrc-2.0 deleted file mode 100644 index 53f3e06..0000000 --- a/reference/home/.gtkrc-2.0 +++ /dev/null @@ -1,14 +0,0 @@ -gtk-enable-animations=1 -gtk-theme-name="Breeze-Dark" -gtk-primary-button-warps-slider=1 -gtk-toolbar-style=3 -gtk-menu-images=1 -gtk-button-images=1 -gtk-cursor-blink-time=1000 -gtk-cursor-blink=1 -gtk-cursor-theme-size=24 -gtk-cursor-theme-name="breeze_cursors" -gtk-sound-theme-name="ocean" -gtk-icon-theme-name="breeze-dark" -gtk-font-name="Noto Sans, 14" - diff --git a/reference/home/.profile b/reference/home/.profile deleted file mode 100644 index c2f3182..0000000 --- a/reference/home/.profile +++ /dev/null @@ -1 +0,0 @@ -. "$HOME/.cargo/env" diff --git a/reference/home/.zshenv b/reference/home/.zshenv deleted file mode 100644 index c2f3182..0000000 --- a/reference/home/.zshenv +++ /dev/null @@ -1 +0,0 @@ -. "$HOME/.cargo/env" diff --git a/reference/home/.zshrc b/reference/home/.zshrc deleted file mode 100644 index 5c34b2c..0000000 --- a/reference/home/.zshrc +++ /dev/null @@ -1 +0,0 @@ -source /usr/share/cachyos-zsh-config/cachyos-config.zsh -- 2.47.3 From 6f9309d329056fc7b176ddcc5c88aab75b123e73 Mon Sep 17 00:00:00 2001 From: alexion Date: Sat, 18 Jul 2026 23:30:48 -0400 Subject: [PATCH 22/32] feat(nvim): configure Neovim via nixvim with Nix-managed plugins Add an nvim Module that configures Neovim declaratively through nixvim, wired as a flake input and consumed as its home-manager module. Options, globals, keymaps, and plugin settings are typed Nix; the colorscheme call and two autocmds live in modules/nvim/config.lua via extraConfigLua. Plugins come from nixpkgs (no plugin manager, no runtime cloning); git, ripgrep, and fd are provided from Nix; treesitter grammars are built by Nix so no runtime compiler is needed. Functionally matches the previous config (plugins, keymaps, options, the nord colorscheme, markdown conceal, the Neogit blame toggle), verified headless against the generated init. --- .claude/tasks/0007-nvim-module.md | 24 ++++ CLAUDE.md | 5 + flake.lock | 62 +++++++++- flake.nix | 7 ++ hosts/neogaia/default.nix | 3 + modules/nvim/config.lua | 27 +++++ modules/nvim/nvim.nix | 191 ++++++++++++++++++++++++++++++ 7 files changed, 318 insertions(+), 1 deletion(-) create mode 100644 .claude/tasks/0007-nvim-module.md create mode 100644 modules/nvim/config.lua create mode 100644 modules/nvim/nvim.nix diff --git a/.claude/tasks/0007-nvim-module.md b/.claude/tasks/0007-nvim-module.md new file mode 100644 index 0000000..27efe18 --- /dev/null +++ b/.claude/tasks/0007-nvim-module.md @@ -0,0 +1,24 @@ +--- +spec: laptop-mvi +blocked-by: 0001-skeleton-and-building-host +--- + +## What to build + +An nvim `Module` that gives the primary user Neovim configured declaratively through **nixvim**, with **functional parity** to the operator's existing config. +Parity is about the "what" — the same plugins, keymaps, options, colorscheme, and behaviour — not the "how". +The mechanism is deliberately free to follow NixOS's declarative paradigm rather than transplanting the imperative lazy.nvim setup: plugins are managed by Nix (no plugin manager, no runtime cloning, no lockfile), and as much of the config as possible is expressed as typed Nix, with raw Lua kept only as an escape hatch. + +## Acceptance criteria + +- [x] An nvim `Module` (following the `Enable convention`) is enabled on `neogaia`. +- [x] Neovim is configured via **nixvim**, wired as a flake input (`nixvim.inputs.nixpkgs.follows = "nixpkgs"`), consumed as its home-manager module under `home-manager.users..programs.nixvim`. +- [x] Functional parity with the previous config: the same plugins (neogit, diffview, gitsigns, oil, snacks, gbprod-nord, render-markdown, which-key, treesitter), keymaps, `vim` options, the `nord` colorscheme, the Neogit blame-toggle autocmd, and markdown concealment — verified headless against the generated config. +- [x] Runtime dependencies `git`, `ripgrep`, and `fd` are provided by Nix; `gcc` is not needed because Nix builds the treesitter grammars. +- [x] The `neogaia` toplevel still builds with the nvim `Module` enabled. + +## Implementation Notes + +- **nixvim, typed Nix first.** `modules/nvim/nvim.nix` enables `programs.nixvim` with `opts`, `globals`, `keymaps`, and typed `plugins.*` settings. Treesitter uses `plugins.treesitter` (`highlight.enable`, `indent.enable`, `grammarPackages` from the module's own `builtGrammars`) covering nix, lua, bash, fish, markdown, rust, python, java, kotlin, c, cpp, html, css, javascript, typescript, go. +- **The imperative remainder** — the `gbprod/nord.nvim` setup + colorscheme call, the markdown `conceallevel` autocmd, and the Neogit blame-toggle `BufUnload` autocmd — lives in `modules/nvim/config.lua`, pulled in via `extraConfigLua = builtins.readFile ./config.lua`. `gbprod-nord` comes in through `extraPlugins` because nixvim's `colorschemes.nord` is a different plugin. +- **Verification.** `nix build .#…programs.nixvim.build.package` exits 0 and the whole toplevel evaluates. The generated config was exercised headless (launched with `-u` the generated init and a scratch `HOME`, since the wrapper otherwise loads the dev host's real `~/.config/nvim`): all options, keymaps, plugins, the `nord` colorscheme, treesitter highlight + indent, and markdown conceal load with no errors. diff --git a/CLAUDE.md b/CLAUDE.md index 72c7c35..2ceb5a0 100644 --- a/CLAUDE.md +++ b/CLAUDE.md @@ -29,3 +29,8 @@ The domain model (Host, Module, Skeleton, Auto-loader, Enable convention, overla - 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`); the forge CLI is `tea` (login `axi`), and `gh` is not installed. +- 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`. +- nixpkgs `vimPlugins.nvim-treesitter` tracks the rewritten `main` branch: there is no `require("nvim-treesitter.configs").setup{ensure_installed,highlight,indent}`. Under nixvim, use `plugins.treesitter` with `highlight.enable`/`indent.enable` and `grammarPackages = with config.programs.nixvim.plugins.treesitter.package.builtGrammars; [ ... ]` — the module's own `package.builtGrammars`, **not** `pkgs.vimPlugins.nvim-treesitter.*` (whose query files can mismatch). The module targets the main branch and enables features via neovim-native APIs (`vim.treesitter.start()`, `require'nvim-treesitter'.indentexpr()`). +- Neovim is configured via **nixvim** (flake input `nixvim`, consumed as `inputs.nixvim.homeModules.nixvim` added to `home-manager.sharedModules`, config under `home-manager.users..programs.nixvim`). `nixvim.inputs.nixpkgs.follows = "nixpkgs"` is set; nixvim then emits a benign eval warning that its pinned nixpkgs differs from the followed one — builds and runs fine, do not "fix" it by dropping the follows. +- To reference the nixvim-built package's own attrs (e.g. treesitter `builtGrammars`) inside our NixOS module, give `home-manager.users.` the module-function form (`hm: { programs.nixvim = { ... hm.config.programs.nixvim... }; }`), since the outer `config` is the NixOS config, not the home-manager one. +- **Verifying a nixvim change headless:** `programs.nixvim.build.package`'s wrapper has **no `-u`**, so running `$OUT/bin/nvim` loads the caller's `~/.config/nvim` (the dev host's real config), *not* the built config — silently. To exercise the built config, launch with `-u "$(nix build --no-link --print-out-paths .#…programs.nixvim.build.initFile)"` and a scratch `HOME`/`XDG_CONFIG_HOME`. `conceallevel` is window-local: set it with `opt_local`/`vim.wo`, never `vim.bo[buf]` (which errors). diff --git a/flake.lock b/flake.lock index c65b3e1..c43e809 100644 --- a/flake.lock +++ b/flake.lock @@ -41,6 +41,27 @@ "type": "github" } }, + "flake-parts": { + "inputs": { + "nixpkgs-lib": [ + "nixvim", + "nixpkgs" + ] + }, + "locked": { + "lastModified": 1782949081, + "narHash": "sha256-vp6Y/Grm98ESt6ceOkWiHWyZRDV3J1RID4w+6NWK9yA=", + "owner": "hercules-ci", + "repo": "flake-parts", + "rev": "17c9d6cdfc60c64f4ee8d306f9bc0b4ccb51481e", + "type": "github" + }, + "original": { + "owner": "hercules-ci", + "repo": "flake-parts", + "type": "github" + } + }, "flake-schemas": { "locked": { "lastModified": 1780327564, @@ -160,6 +181,28 @@ "type": "github" } }, + "nixvim": { + "inputs": { + "flake-parts": "flake-parts", + "nixpkgs": [ + "nixpkgs" + ], + "systems": "systems" + }, + "locked": { + "lastModified": 1784057377, + "narHash": "sha256-yycNej5//EsRbV10moBoh+/63vXEwZD1ZFEiRm6C9rQ=", + "owner": "nix-community", + "repo": "nixvim", + "rev": "07180a087e4a00720dc0731cbcd8dec796974381", + "type": "github" + }, + "original": { + "owner": "nix-community", + "repo": "nixvim", + "type": "github" + } + }, "root": { "inputs": { "chaotic": "chaotic", @@ -167,7 +210,24 @@ "home-manager": "home-manager_2", "nixpkgs": "nixpkgs_2", "nixpkgs-stable": "nixpkgs-stable", - "nixpkgs-unstable": "nixpkgs-unstable" + "nixpkgs-unstable": "nixpkgs-unstable", + "nixvim": "nixvim" + } + }, + "systems": { + "locked": { + "lastModified": 1774449309, + "narHash": "sha256-brhZ8DmuGtzkCYHJg4HEd602amKm89Y9ytsFZ5uWD1w=", + "owner": "nix-systems", + "repo": "default", + "rev": "c29398b59d2048c4ab79345812849c9bd15e9150", + "type": "github" + }, + "original": { + "owner": "nix-systems", + "ref": "future-26.11", + "repo": "default", + "type": "github" } } }, diff --git a/flake.nix b/flake.nix index e4a224e..0500a27 100644 --- a/flake.nix +++ b/flake.nix @@ -16,6 +16,13 @@ inputs.nixpkgs.follows = "nixpkgs"; }; + # Neovim configured declaratively in Nix. Must follow our nixpkgs so its + # plugins build against the same package set. + nixvim = { + url = "github:nix-community/nixvim"; + inputs.nixpkgs.follows = "nixpkgs"; + }; + # Declarative disk partitioning. Each Host declares its own layout; a Host # that preserves an existing pool simply declares none. disko = { diff --git a/hosts/neogaia/default.nix b/hosts/neogaia/default.nix index c877b48..4991ea9 100644 --- a/hosts/neogaia/default.nix +++ b/hosts/neogaia/default.nix @@ -42,6 +42,9 @@ # tmux as the terminal multiplexer. modules.tmux.enable = true; + # Neovim, configured declaratively via nixvim. + modules.nvim.enable = true; + # Locale preferences for the base system. time.timeZone = "America/New_York"; i18n.defaultLocale = "en_GB.UTF-8"; diff --git a/modules/nvim/config.lua b/modules/nvim/config.lua new file mode 100644 index 0000000..ed161bd --- /dev/null +++ b/modules/nvim/config.lua @@ -0,0 +1,27 @@ +-- Imperative configuration that has no typed nixvim option: the colorscheme +-- call and two autocmds. Everything expressible as Nix lives in ./nvim.nix. + +-- gbprod/nord.nvim, provided as an extra plugin from nixpkgs. +require("nord").setup({ + transparent = true, +}) +vim.cmd.colorscheme("nord") + +-- Conceal markdown syntax in markdown buffers (previously an after/ftplugin). +-- conceallevel is window-local, so it is set with opt_local when the filetype +-- is applied to the buffer's window. +vim.api.nvim_create_autocmd("FileType", { + pattern = "markdown", + callback = function() + vim.opt_local.conceallevel = 2 + end, +}) + +-- Turn line blame off again once the Neogit status buffer is closed. +vim.api.nvim_create_autocmd("BufUnload", { + callback = function(args) + if vim.bo[args.buf].filetype == "NeogitStatus" then + require("gitsigns").toggle_current_line_blame(false) + end + end, +}) diff --git a/modules/nvim/nvim.nix b/modules/nvim/nvim.nix new file mode 100644 index 0000000..f261255 --- /dev/null +++ b/modules/nvim/nvim.nix @@ -0,0 +1,191 @@ +{ + config, + lib, + pkgs, + inputs, + ... +}: +# Neovim for the primary user, configured declaratively through nixvim. Options, +# keymaps, and plugin settings are typed Nix; the imperative remainder (the +# colorscheme call and the Neogit blame-toggle autocmd) lives in ./config.lua. +# Plugins come from nixpkgs — no plugin manager and no runtime cloning — and +# treesitter grammars are built by Nix, so no compiler is needed at runtime. git +# backs the git plugins; ripgrep and fd back the picker. +let + cfg = config.modules.nvim; + user = config.user.name; +in +{ + options.modules.nvim.enable = lib.mkEnableOption "Neovim, configured declaratively via nixvim"; + + config = lib.mkIf cfg.enable { + home-manager.sharedModules = [ inputs.nixvim.homeModules.nixvim ]; + + home-manager.users.${user} = hm: { + programs.nixvim = { + enable = true; + + extraPackages = with pkgs; [ + git # neogit and gitsigns shell out to git + ripgrep # backs the picker's live grep + fd # backs the picker's file finder + ]; + + globals.mapleader = " "; + globals.clipboard = "osc52"; # neovim's built-in OSC 52 provider, no external binary needed + + opts = { + clipboard = "unnamedplus"; + + number = true; + relativenumber = true; + + shiftwidth = 2; + tabstop = 2; + expandtab = true; + + mouse = "a"; + + undofile = true; + + ignorecase = true; + smartcase = true; + + splitright = true; + splitbelow = true; + + wrap = false; + + scrolloff = 8; + cursorline = true; + }; + + keymaps = [ + { + mode = "n"; + key = ""; + action = "h"; + options.desc = "Move focus left"; + } + { + mode = "n"; + key = ""; + action = "j"; + options.desc = "Move focus down"; + } + { + mode = "n"; + key = ""; + action = "k"; + options.desc = "Move focus up"; + } + { + mode = "n"; + key = ""; + action = "l"; + options.desc = "Move focus right"; + } + { + mode = "n"; + key = ""; + action = "nohlsearch"; + options.desc = "Clear search highlight"; + } + { + mode = "n"; + key = "e"; + action = "Oil"; + options.desc = "Open file browser"; + } + { + mode = "n"; + key = "f"; + action.__raw = "function() require('snacks').picker.files() end"; + options.desc = "Find files"; + } + { + mode = "n"; + key = "s"; + action.__raw = "function() require('snacks').picker.grep() end"; + options.desc = "Search text"; + } + { + mode = "n"; + key = "b"; + action.__raw = "function() require('snacks').picker.buffers() end"; + options.desc = "Switch buffer"; + } + { + mode = "n"; + key = "g"; + action.__raw = '' + function() + require('gitsigns').toggle_current_line_blame(true) + require('neogit').open() + end + ''; + options.desc = "Open git (Neogit)"; + } + ]; + + plugins = { + gitsigns = { + enable = true; + settings.current_line_blame = false; + }; + + neogit.enable = true; + diffview.enable = true; # neogit's diff integration + + oil = { + enable = true; + settings.view_options.show_hidden = true; + }; + + snacks = { + enable = true; + settings = { + picker.enabled = true; + notifier.enabled = true; + input.enabled = true; + }; + }; + + which-key.enable = true; + render-markdown.enable = true; + + treesitter = { + enable = true; + highlight.enable = true; + indent.enable = true; + grammarPackages = with hm.config.programs.nixvim.plugins.treesitter.package.builtGrammars; [ + nix + lua + bash + fish + markdown + markdown_inline + rust + python + java + kotlin + c + cpp + html + css + javascript + typescript + go + ]; + }; + }; + + # gbprod/nord.nvim; nixvim's colorschemes.nord is a different plugin. Set + # up in ./config.lua. + extraPlugins = [ pkgs.vimPlugins.gbprod-nord ]; + + extraConfigLua = builtins.readFile ./config.lua; + }; + }; + }; +} -- 2.47.3 From 505002bb2b652a2d21491da271983dc414b1b910 Mon Sep 17 00:00:00 2001 From: alexion Date: Sat, 18 Jul 2026 23:49:05 -0400 Subject: [PATCH 23/32] feat(claude-code): add a Module and enable it on neogaia MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Install Claude Code through home-manager's native programs.claude-code module, matching how the tmux and fish Modules use their home-manager options rather than a raw home.packages entry. The feature Module at modules/claude-code/claude-code.nix stays thin — just the enable option and the delegation — and writes no settings, so login and first-run configuration remain interactive and no auth material enters the repo. Signing in without a browser (needed over the console or SSH) is documented in modules/claude-code/authentication.md: the paste-code OAuth flow, where the printed URL is opened on another device and the code pasted back, and the ANTHROPIC_API_KEY path for non-interactive use. The neogaia toplevel builds with claude-code-2.1.209 included. --- .claude/tasks/0008-claude-code-module.md | 22 ++++++++++++++++ hosts/neogaia/default.nix | 3 +++ modules/claude-code/authentication.md | 32 ++++++++++++++++++++++++ modules/claude-code/claude-code.nix | 22 ++++++++++++++++ 4 files changed, 79 insertions(+) create mode 100644 .claude/tasks/0008-claude-code-module.md create mode 100644 modules/claude-code/authentication.md create mode 100644 modules/claude-code/claude-code.nix diff --git a/.claude/tasks/0008-claude-code-module.md b/.claude/tasks/0008-claude-code-module.md new file mode 100644 index 0000000..10349a5 --- /dev/null +++ b/.claude/tasks/0008-claude-code-module.md @@ -0,0 +1,22 @@ +--- +spec: laptop-mvi +blocked-by: 0001-skeleton-and-building-host +--- + +## What to build + +Install Claude Code declaratively on `neogaia`, and make it authenticatable without a browser on the laptop so it can be used over the console/SSH via the paste-code flow or an API key. + +## Acceptance criteria + +- [x] Claude Code is installed declaratively (following the `Enable convention` if expressed as a `Module`) and enabled on `neogaia`. +- [x] The browserless authentication path (paste-code flow or API key) is documented so it works over console/SSH. +- [x] The `neogaia` toplevel still builds with Claude Code included. + +## Implementation Notes + +- **Native home-manager module, not a raw package.** Claude Code is enabled through home-manager's own `programs.claude-code` module (`home-manager.users..programs.claude-code.enable = true`), mirroring how `tmux`/`fish` use their native home-manager options rather than dropping a package into `home.packages`. The module ships within home-manager itself, so — unlike `nvim`/nixvim — no new flake input is needed. Per the invocation's steer to prefer the tmux/nvim conventions over the task wording, the feature `Module` at `modules/claude-code/claude-code.nix` is kept as thin as the `tmux` module: just the `enable` option and the delegation. +- **No settings written.** The module manages no `~/.claude` contents and writes no `settings.json`, so login and first-run configuration stay interactive. This keeps auth material (subscription token or API key) out of the repo. +- **Auth docs co-located with the module.** The browserless authentication guide lives at `modules/claude-code/authentication.md`, next to the module, following the repo pattern where each module directory holds its own supporting files. It covers both the paste-code OAuth flow (open the printed URL on another device, paste the code back — works unchanged over SSH) and the `ANTHROPIC_API_KEY` path. This is distinct from task 0009's OS-install docs, which cover `disko-install`, not the CLI login. +- **Verification.** `nix build .#checks.x86_64-linux.neogaia` (the primary Host seam) builds the toplevel with `claude-code-2.1.209` included; `config.modules.claude-code.enable` and the home-manager `programs.claude-code.enable` both evaluate `true`. +- **Note on flake evaluation.** The new module file had to be `git add`ed before the flake could see it — flakes evaluate the git tree, so an untracked Module is invisible to the Auto-loader and the host errors with "option does not exist". diff --git a/hosts/neogaia/default.nix b/hosts/neogaia/default.nix index 4991ea9..c8b7778 100644 --- a/hosts/neogaia/default.nix +++ b/hosts/neogaia/default.nix @@ -45,6 +45,9 @@ # Neovim, configured declaratively via nixvim. modules.nvim.enable = true; + # Claude Code, Anthropic's CLI, installed via home-manager. + modules.claude-code.enable = true; + # Locale preferences for the base system. time.timeZone = "America/New_York"; i18n.defaultLocale = "en_GB.UTF-8"; diff --git a/modules/claude-code/authentication.md b/modules/claude-code/authentication.md new file mode 100644 index 0000000..316e81e --- /dev/null +++ b/modules/claude-code/authentication.md @@ -0,0 +1,32 @@ +# Authenticating Claude Code without a browser + +`neogaia` is driven from the console and over SSH, where no local browser can service Claude Code's default OAuth redirect. +Either of the two paths below signs the CLI in from a bare terminal. +Both are one-time actions per machine; the credentials land under `~/.claude`, which home-manager does not overwrite. + +## Paste-code flow (Claude subscription or Console OAuth) + +Run `claude` and start the login with the `/login` command (the first run offers it automatically). +On a machine with no browser it cannot open the authorization page itself, so it prints the authorization URL and waits. + +1. Copy the printed URL to a browser on any other device (phone, another laptop). +2. Sign in and approve the request there. +3. The page returns a short authorization code; paste it back at the `claude` prompt still waiting in the terminal. + +The session then completes and the token is stored, so later runs need no further login. +Because the URL is opened on a *different* device, this works unchanged over SSH. + +## API key + +For non-interactive use, set an Anthropic API key from in the environment before launching `claude`: + +```console +$ export ANTHROPIC_API_KEY=sk-ant-... +$ claude +``` + +Claude Code reads `ANTHROPIC_API_KEY` on startup and skips the interactive login entirely, so this path needs neither a browser nor the paste-code exchange. +Usage is billed to the Console account that owns the key rather than to a Claude subscription. + +The key is a secret and is deliberately not baked into this configuration. +Export it from the shell for a one-off, or source it from a secret store once one exists on the Host. diff --git a/modules/claude-code/claude-code.nix b/modules/claude-code/claude-code.nix new file mode 100644 index 0000000..fa26140 --- /dev/null +++ b/modules/claude-code/claude-code.nix @@ -0,0 +1,22 @@ +{ + config, + lib, + ... +}: +# Claude Code — Anthropic's CLI — for the primary user, installed declaratively +# through home-manager. home-manager ships the package and owns ~/.claude; no +# settings are written here, so login and first-run configuration stay +# interactive. Signing in without a browser, as needed over the console or SSH, +# is covered in ./authentication.md. +let + cfg = config.modules.claude-code; + user = config.user.name; +in +{ + options.modules.claude-code.enable = + lib.mkEnableOption "Claude Code, Anthropic's CLI, installed via home-manager"; + + config = lib.mkIf cfg.enable { + home-manager.users.${user}.programs.claude-code.enable = true; + }; +} -- 2.47.3 From b7363ed7e1613a69045134cfcdf199e1d7192cc5 Mon Sep 17 00:00:00 2001 From: alexion Date: Sun, 19 Jul 2026 00:06:24 -0400 Subject: [PATCH 24/32] feat(claude-code): manage the user's global config in the Module Bring the declarative half of ~/.claude into modules/claude-code and apply it when the Module is enabled: the global agent instructions (context = ./CLAUDE.md), the skills tree (skills = ./skills), the attention-bell hook, and settings.json (model = opus plus the Stop/Notification/SessionStart hook wiring). Runtime state (projects, plugins, cache, history, sessions) and the .credentials.json secret are left out, so login survives rebuilds and no secret enters the repo. Verified against the built home-files that ~/.claude/{CLAUDE.md,settings.json,skills,hooks/attention-bell.sh} are generated, the hook executable. --- .claude/tasks/0008-claude-code-module.md | 1 + modules/claude-code/CLAUDE.md | 37 ++++ modules/claude-code/claude-code.nix | 63 +++++- modules/claude-code/hooks/attention-bell.sh | 21 ++ .../skills/codebase-design/DEEPENING.md | 37 ++++ .../skills/codebase-design/DESIGN-IT-TWICE.md | 44 ++++ .../skills/codebase-design/SKILL.md | 113 ++++++++++ .../skills/craft-skill/GLOSSARY.md | 195 ++++++++++++++++++ .../claude-code/skills/craft-skill/SKILL.md | 49 +++++ .../skills/domain-modeling/ADR-FORMAT.md | 47 +++++ .../skills/domain-modeling/CONTEXT-FORMAT.md | 30 +++ .../skills/domain-modeling/SKILL.md | 56 +++++ modules/claude-code/skills/gitea-axi/SKILL.md | 48 +++++ modules/claude-code/skills/grill/SKILL.md | 20 ++ modules/claude-code/skills/implement/SKILL.md | 72 +++++++ .../skills/improve-codebase/HTML-REPORT.md | 119 +++++++++++ .../skills/improve-codebase/SKILL.md | 68 ++++++ .../claude-code/skills/library/nbdev/SKILL.md | 152 ++++++++++++++ .../claude-code/skills/remove-skills/SKILL.md | 37 ++++ .../skills/review-uncommitted/SKILL.md | 147 +++++++++++++ .../skills/setup-skills/LOCKFILE.md | 53 +++++ .../claude-code/skills/setup-skills/SKILL.md | 46 +++++ .../skills/setup-skills/hash-dir.sh | 23 +++ .../skills/test-driven-development/SKILL.md | 153 ++++++++++++++ .../skills/test-driven-development/mocking.md | 59 ++++++ .../test-driven-development/refactoring.md | 10 + .../skills/test-driven-development/tests.md | 77 +++++++ modules/claude-code/skills/to-spec/SKILL.md | 21 ++ .../claude-code/skills/to-spec/SPEC-FORMAT.md | 55 +++++ modules/claude-code/skills/to-tasks/SKILL.md | 57 +++++ .../skills/to-tasks/TASK-FORMAT.md | 28 +++ .../claude-code/skills/update-skills/SKILL.md | 52 +++++ 32 files changed, 1982 insertions(+), 8 deletions(-) create mode 100644 modules/claude-code/CLAUDE.md create mode 100755 modules/claude-code/hooks/attention-bell.sh create mode 100644 modules/claude-code/skills/codebase-design/DEEPENING.md create mode 100644 modules/claude-code/skills/codebase-design/DESIGN-IT-TWICE.md create mode 100644 modules/claude-code/skills/codebase-design/SKILL.md create mode 100644 modules/claude-code/skills/craft-skill/GLOSSARY.md create mode 100644 modules/claude-code/skills/craft-skill/SKILL.md create mode 100644 modules/claude-code/skills/domain-modeling/ADR-FORMAT.md create mode 100644 modules/claude-code/skills/domain-modeling/CONTEXT-FORMAT.md create mode 100644 modules/claude-code/skills/domain-modeling/SKILL.md create mode 100644 modules/claude-code/skills/gitea-axi/SKILL.md create mode 100644 modules/claude-code/skills/grill/SKILL.md create mode 100644 modules/claude-code/skills/implement/SKILL.md create mode 100644 modules/claude-code/skills/improve-codebase/HTML-REPORT.md create mode 100644 modules/claude-code/skills/improve-codebase/SKILL.md create mode 100644 modules/claude-code/skills/library/nbdev/SKILL.md create mode 100644 modules/claude-code/skills/remove-skills/SKILL.md create mode 100644 modules/claude-code/skills/review-uncommitted/SKILL.md create mode 100644 modules/claude-code/skills/setup-skills/LOCKFILE.md create mode 100644 modules/claude-code/skills/setup-skills/SKILL.md create mode 100755 modules/claude-code/skills/setup-skills/hash-dir.sh create mode 100644 modules/claude-code/skills/test-driven-development/SKILL.md create mode 100644 modules/claude-code/skills/test-driven-development/mocking.md create mode 100644 modules/claude-code/skills/test-driven-development/refactoring.md create mode 100644 modules/claude-code/skills/test-driven-development/tests.md create mode 100644 modules/claude-code/skills/to-spec/SKILL.md create mode 100644 modules/claude-code/skills/to-spec/SPEC-FORMAT.md create mode 100644 modules/claude-code/skills/to-tasks/SKILL.md create mode 100644 modules/claude-code/skills/to-tasks/TASK-FORMAT.md create mode 100644 modules/claude-code/skills/update-skills/SKILL.md diff --git a/.claude/tasks/0008-claude-code-module.md b/.claude/tasks/0008-claude-code-module.md index 10349a5..7504f87 100644 --- a/.claude/tasks/0008-claude-code-module.md +++ b/.claude/tasks/0008-claude-code-module.md @@ -20,3 +20,4 @@ Install Claude Code declaratively on `neogaia`, and make it authenticatable with - **Auth docs co-located with the module.** The browserless authentication guide lives at `modules/claude-code/authentication.md`, next to the module, following the repo pattern where each module directory holds its own supporting files. It covers both the paste-code OAuth flow (open the printed URL on another device, paste the code back — works unchanged over SSH) and the `ANTHROPIC_API_KEY` path. This is distinct from task 0009's OS-install docs, which cover `disko-install`, not the CLI login. - **Verification.** `nix build .#checks.x86_64-linux.neogaia` (the primary Host seam) builds the toplevel with `claude-code-2.1.209` included; `config.modules.claude-code.enable` and the home-manager `programs.claude-code.enable` both evaluate `true`. - **Note on flake evaluation.** The new module file had to be `git add`ed before the flake could see it — flakes evaluate the git tree, so an untracked Module is invisible to the Auto-loader and the host errors with "option does not exist". +- **Personal config ported into the Module (beyond the acceptance criteria).** At the operator's request the declarative half of `~/.claude` now lives in the Module and is applied when it is enabled: the global agent instructions (`context = ./CLAUDE.md`), the skills tree (`skills = ./skills`, 15 skills), the attention-bell hook (`hooks."attention-bell.sh"`), and `settings.json` (`model = "opus"` plus the Stop/Notification/SessionStart hook wiring). Runtime state (`projects/`, `plugins/`, `cache/`, `history.jsonl`, sessions) and the `~/.claude/.credentials.json` secret are deliberately left out, so login survives rebuilds and no secret enters the repo. Stale `agents`/`commands` symlinks (into an outdated `~/wrk/claude`) were skipped. Verified against the built `home-files`: `~/.claude/{CLAUDE.md,settings.json,skills/,hooks/attention-bell.sh}` are all generated, with the hook executable. The `gitea-axi` SessionStart hook depends on that binary being on `PATH`; the flake does not yet provide it, so the hook is a no-op on a host until it is installed. diff --git a/modules/claude-code/CLAUDE.md b/modules/claude-code/CLAUDE.md new file mode 100644 index 0000000..7c341e5 --- /dev/null +++ b/modules/claude-code/CLAUDE.md @@ -0,0 +1,37 @@ +# Alexion's Agent Instructions + +These are common instructions for Alexion's agents across all scenarios. + +## General Guidelines + +- When writing commit messages, NEVER auto-add your agent name as co-author. + Omit the `Co-Authored-By:` trailer entirely, with no exceptions. + This overrides any default instruction to append one. +- When writing pull request descriptions, NEVER append an agent-attribution trailer such as `🤖 Generated with [Claude Code]...`. + Leave it out entirely, with no exceptions. + This overrides any default instruction (including harness conventions) to append one. +- Never manually modify CHANGELOG.md files or any files that are marked as auto-generated. + Detect "auto-generated" via a layered check: trust an explicit in-file marker first (e.g. `AUTO-GENERATED, DO NOT EDIT`). + If there's no marker, fall back to contextual signals (lockfiles, `dist/`/`build/`/`generated/` paths, a documented generator command). + If it's still ambiguous, ask before editing rather than guessing. +- When writing or substantially editing long Markdown files, put each full sentence in its own line. + Preserve normal Markdown structure, but avoid wrapping multiple sentences onto one physical line. + Apply this to any prose you author, regardless of file length; "long" is not a real threshold. + Only format what you're actually writing or changing. + Never reflow an entire pre-existing paragraph or file just because you touched something nearby. +- When making technical decisions, do not give much weight to development cost. + Instead, prefer quality, simplicity, robustness, scalability and long term maintainability. + This is specifically about implementation time. + Human cost/benefit heuristics ("not worth N extra days of engineering") don't transfer to an AI agent that codes far faster than a human. + This is not a license to override standard anti-overengineering guardrails (avoid premature abstraction, no speculative config, etc.); those still apply to unnecessary complexity. + It means: don't discount a more robust or maintainable approach just because it would take a human a long time to build. +- File names should always be lower case, unless there's a valid reason. + Established ecosystem or tool conventions count as a valid reason automatically (e.g. `README.md`, `LICENSE`, `CHANGELOG.md`, `Makefile`, `Dockerfile`, `.github/` files), without needing to ask each time. +- When you discover that a belief you held about an objective fact or convention of the current project was wrong, write it down so it isn't relearned next time. + This applies whether the user corrected you or you caught the mistake yourself, and only to things that are true regardless of who is operating the project (a wrong build command, a wrong file path, a convention you guessed at instead of checking) — not personal working-style preferences or one-off task details. + Record it in that project's own CLAUDE.md, not this global file, under a dedicated `## Gotchas` section (create the section if the file doesn't have one yet). + If the project has nested CLAUDE.md files, use the one nearest to where the mistake occurred, falling back to the project's top-level CLAUDE.md. + Append to an existing CLAUDE.md immediately, without asking; if no CLAUDE.md exists yet for the project, ask before creating one. + Briefly mention the edit in your response rather than making it silently. + If an existing entry is later found to be wrong or stale, correct or remove it the same way. + diff --git a/modules/claude-code/claude-code.nix b/modules/claude-code/claude-code.nix index fa26140..c26c089 100644 --- a/modules/claude-code/claude-code.nix +++ b/modules/claude-code/claude-code.nix @@ -3,20 +3,67 @@ lib, ... }: -# Claude Code — Anthropic's CLI — for the primary user, installed declaratively -# through home-manager. home-manager ships the package and owns ~/.claude; no -# settings are written here, so login and first-run configuration stay -# interactive. Signing in without a browser, as needed over the console or SSH, -# is covered in ./authentication.md. +# Claude Code — Anthropic's CLI — for the primary user, configured declaratively +# through home-manager. home-manager ships the package and manages ~/.claude: +# the global agent instructions (./CLAUDE.md), the skills tree (./skills), the +# attention-bell hook (./hooks), and settings.json (the model and the hook +# wiring). Login credentials are left unmanaged so they survive rebuilds; +# signing in without a browser, as needed over the console or SSH, is covered in +# ./authentication.md. let cfg = config.modules.claude-code; user = config.user.name; + + # Rings the terminal bell so tmux flags the background pane; wired to both the + # end of a turn and attention notifications below. + bellHook = [ + { + hooks = [ + { + type = "command"; + command = "~/.claude/hooks/attention-bell.sh"; + } + ]; + } + ]; in { - options.modules.claude-code.enable = - lib.mkEnableOption "Claude Code, Anthropic's CLI, installed via home-manager"; + options.modules.claude-code.enable = lib.mkEnableOption "Claude Code, Anthropic's CLI, configured via home-manager"; config = lib.mkIf cfg.enable { - home-manager.users.${user}.programs.claude-code.enable = true; + home-manager.users.${user}.programs.claude-code = { + enable = true; + + # Global agent instructions, rendered to ~/.claude/CLAUDE.md. + context = ./CLAUDE.md; + + # One directory per skill, each carrying its SKILL.md, symlinked under + # ~/.claude/skills. + skills = ./skills; + + # Installed executable at ~/.claude/hooks/attention-bell.sh, where the + # settings hooks reference it. + hooks."attention-bell.sh" = builtins.readFile ./hooks/attention-bell.sh; + + settings = { + model = "opus"; + hooks = { + Stop = bellHook; + Notification = bellHook; + SessionStart = [ + { + matcher = ""; + hooks = [ + { + type = "command"; + command = "gitea-axi"; + timeout = 10; + } + ]; + } + ]; + }; + }; + }; }; } diff --git a/modules/claude-code/hooks/attention-bell.sh b/modules/claude-code/hooks/attention-bell.sh new file mode 100755 index 0000000..0b83487 --- /dev/null +++ b/modules/claude-code/hooks/attention-bell.sh @@ -0,0 +1,21 @@ +#!/bin/sh +# attention-bell.sh — ring the terminal bell in this Claude session's tmux pane +# so tmux's monitor-bell flags the (background) window red in the status bar. +# +# Claude Code runs hooks as detached subprocesses: they have no controlling +# terminal, so /dev/tty is unavailable here. But the parent-process chain up to +# the `claude` process stays intact, and `claude` itself holds the pane's pty. +# So we walk ancestry to find it and write the bell straight to that tty. +# (Writing a bare BEL to an explicit /dev/pts/N works even from a detached +# process — verified against tmux's window_bell_flag.) + +pid=$PPID +while [ "$pid" -gt 1 ] 2>/dev/null; do + if [ "$(ps -o comm= -p "$pid" 2>/dev/null)" = claude ]; then + tty=$(ps -o tty= -p "$pid" 2>/dev/null | tr -d ' ') + [ -n "$tty" ] && [ "$tty" != '?' ] && printf '\a' > "/dev/$tty" + exit 0 + fi + pid=$(ps -o ppid= -p "$pid" 2>/dev/null | tr -d ' ') + [ -z "$pid" ] && break +done diff --git a/modules/claude-code/skills/codebase-design/DEEPENING.md b/modules/claude-code/skills/codebase-design/DEEPENING.md new file mode 100644 index 0000000..3938457 --- /dev/null +++ b/modules/claude-code/skills/codebase-design/DEEPENING.md @@ -0,0 +1,37 @@ +# Deepening + +How to deepen a cluster of shallow modules safely, given its dependencies. Assumes the vocabulary in [SKILL.md](SKILL.md) — **module**, **interface**, **seam**, **adapter**. + +## Dependency categories + +When assessing a candidate for deepening, classify its dependencies. The category determines how the deepened module is tested across its seam. + +### 1. In-process + +Pure computation, in-memory state, no I/O. Always deepenable — merge the modules and test through the new interface directly. No adapter needed. + +### 2. Local-substitutable + +Dependencies that have local test stand-ins (PGLite for Postgres, in-memory filesystem). Deepenable if the stand-in exists. The deepened module is tested with the stand-in running in the test suite. The seam is internal; no port at the module's external interface. + +### 3. Remote but owned (Ports & Adapters) + +Your own services across a network boundary (microservices, internal APIs). Define a **port** (interface) at the seam. The deep module owns the logic; the transport is injected as an **adapter**. Tests use an in-memory adapter. Production uses an HTTP/gRPC/queue adapter. + +Recommendation shape: *"Define a port at the seam, implement an HTTP adapter for production and an in-memory adapter for testing, so the logic sits in one deep module even though it's deployed across a network."* + +### 4. True external (Mock) + +Third-party services (Stripe, Twilio, etc.) you don't control. The deepened module takes the external dependency as an injected port; tests provide a mock adapter. + +## Seam discipline + +- **One adapter means a hypothetical seam. Two adapters means a real one.** Don't introduce a port unless at least two adapters are justified (typically production + test). A single-adapter seam is just indirection. +- **Internal seams vs external seams.** A deep module can have internal seams (private to its implementation, used by its own tests) as well as the external seam at its interface. Don't expose internal seams through the interface just because tests use them. + +## Testing strategy: replace, don't layer + +- Old unit tests on shallow modules become waste once tests at the deepened module's interface exist — delete them. +- Write new tests at the deepened module's interface. The **interface is the test surface**. +- Tests assert on observable outcomes through the interface, not internal state. +- Tests should survive internal refactors — they describe behaviour, not implementation. If a test has to change when the implementation changes, it's testing past the interface. diff --git a/modules/claude-code/skills/codebase-design/DESIGN-IT-TWICE.md b/modules/claude-code/skills/codebase-design/DESIGN-IT-TWICE.md new file mode 100644 index 0000000..d41f678 --- /dev/null +++ b/modules/claude-code/skills/codebase-design/DESIGN-IT-TWICE.md @@ -0,0 +1,44 @@ +# Design It Twice + +When the user wants to explore alternative interfaces for a chosen deepening candidate, use this parallel sub-agent pattern. Based on "Design It Twice" (Ousterhout) — your first idea is unlikely to be the best. + +Uses the vocabulary in [SKILL.md](SKILL.md) — **module**, **interface**, **seam**, **adapter**, **leverage**. + +## Process + +### 1. Frame the problem space + +Before spawning sub-agents, write a user-facing explanation of the problem space for the chosen candidate: + +- The constraints any new interface would need to satisfy +- The dependencies it would rely on, and which category they fall into (see [DEEPENING.md](DEEPENING.md)) +- A rough illustrative code sketch to ground the constraints — not a proposal, just a way to make the constraints concrete + +Show this to the user, then immediately proceed to Step 2. The user reads and thinks while the sub-agents work in parallel. + +### 2. Spawn sub-agents + +Spawn 3+ sub-agents in parallel using the Agent tool. Each must produce a **radically different** interface for the deepened module. + +Prompt each sub-agent with a separate technical brief (file paths, coupling details, dependency category from [DEEPENING.md](DEEPENING.md), what sits behind the seam). The brief is independent of the user-facing problem-space explanation in Step 1. Give each agent a different design constraint: + +- Agent 1: "Minimize the interface — aim for 1–3 entry points max. Maximise leverage per entry point." +- Agent 2: "Maximise flexibility — support many use cases and extension." +- Agent 3: "Optimise for the most common caller — make the default case trivial." +- Agent 4 (if applicable): "Design around ports & adapters for cross-seam dependencies." + +Include both [SKILL.md](SKILL.md) vocabulary and `.claude/CONTEXT.md` vocabulary in the brief so each sub-agent names things consistently with the architecture language and the project's domain language. + +Each sub-agent outputs: + +1. Interface (types, methods, params — plus invariants, ordering, error modes) +2. Usage example showing how callers use it +3. What the implementation hides behind the seam +4. Dependency strategy and adapters (see [DEEPENING.md](DEEPENING.md)) +5. Trade-offs — where leverage is high, where it's thin + +### 3. Present and compare + +Present designs sequentially so the user can absorb each one, then compare them in prose. Contrast by **depth** (leverage at the interface), **locality** (where change concentrates), and **seam placement**. + +After comparing, give your own recommendation: which design you think is strongest and why. If elements from different designs would combine well, propose a hybrid. Be opinionated — the user wants a strong read, not a menu. diff --git a/modules/claude-code/skills/codebase-design/SKILL.md b/modules/claude-code/skills/codebase-design/SKILL.md new file mode 100644 index 0000000..cdc3a74 --- /dev/null +++ b/modules/claude-code/skills/codebase-design/SKILL.md @@ -0,0 +1,113 @@ +--- +name: codebase-design +description: Shared vocabulary for designing deep modules. Use when the user wants to design or improve a module's interface, find deepening opportunities, decide where a seam goes, make code more testable or AI-navigable, or when another skill needs the deep-module vocabulary. +--- + +# Codebase Design + +Design **deep modules**: a lot of behaviour behind a small interface, placed at a clean seam, testable through that interface. Use this language and these principles wherever code is being designed or restructured. The aim is leverage for callers, locality for maintainers, and testability for everyone. + +## Glossary + +Use these terms exactly — don't substitute "component," "service," "API," or "boundary." Consistent language is the whole point. + +**Module** — anything with an interface and an implementation. Deliberately scale-agnostic: a function, class, package, or tier-spanning slice. _Avoid_: unit, component, service. + +**Interface** — everything a caller must know to use the module correctly: the type signature, but also invariants, ordering constraints, error modes, required configuration, and performance characteristics. _Avoid_: API, signature (too narrow — they refer only to the type-level surface). + +**Implementation** — what's inside a module, its body of code. Distinct from **Adapter**: a thing can be a small adapter with a large implementation (a Postgres repository) or a large adapter with a small implementation (an in-memory fake). Reach for "adapter" when the seam is the topic; "implementation" otherwise. + +**Depth** — leverage at the interface: the amount of behaviour a caller (or test) can exercise per unit of interface they have to learn. A module is **deep** when a large amount of behaviour sits behind a small interface, **shallow** when the interface is nearly as complex as the implementation. + +**Seam** _(Michael Feathers)_ — a place where you can alter behaviour without editing in that place; the *location* at which a module's interface lives. Where to put the seam is its own design decision, distinct from what goes behind it. _Avoid_: boundary (overloaded with DDD's bounded context). + +**Adapter** — a concrete thing that satisfies an interface at a seam. Describes *role* (what slot it fills), not substance (what's inside). + +**Leverage** — what callers get from depth: more capability per unit of interface they learn. One implementation pays back across N call sites and M tests. + +**Locality** — what maintainers get from depth: change, bugs, knowledge, and verification concentrate in one place rather than spreading across callers. Fix once, fixed everywhere. + +## Deep vs shallow + +**Deep module** = small interface + lots of implementation: + +``` +┌─────────────────────┐ +│ Small Interface │ ← Few methods, simple params +├─────────────────────┤ +│ │ +│ Deep Implementation│ ← Complex logic hidden +│ │ +└─────────────────────┘ +``` + +**Shallow module** = large interface + little implementation (avoid): + +``` +┌─────────────────────────────────┐ +│ Large Interface │ ← Many methods, complex params +├─────────────────────────────────┤ +│ Thin Implementation │ ← Just passes through +└─────────────────────────────────┘ +``` + +When designing an interface, ask: + +- Can I reduce the number of methods? +- Can I simplify the parameters? +- Can I hide more complexity inside? + +## Principles + +- **Depth is a property of the interface, not the implementation.** A deep module can be internally composed of small, mockable, swappable parts — they just aren't part of the interface. A module can have **internal seams** (private to its implementation, used by its own tests) as well as the **external seam** at its interface. +- **The deletion test.** Imagine deleting the module. If complexity vanishes, it was a pass-through. If complexity reappears across N callers, it was earning its keep. +- **The interface is the test surface.** Callers and tests cross the same seam. If you want to test *past* the interface, the module is probably the wrong shape. +- **One adapter means a hypothetical seam. Two adapters means a real one.** Don't introduce a seam unless something actually varies across it. + +## Designing for testability + +Good interfaces make testing natural: + +1. **Accept dependencies, don't create them.** + + ```typescript + // Testable + function processOrder(order, paymentGateway) {} + + // Hard to test + function processOrder(order) { + const gateway = new StripeGateway(); + } + ``` + +2. **Return results, don't produce side effects.** + + ```typescript + // Testable + function calculateDiscount(cart): Discount {} + + // Hard to test + function applyDiscount(cart): void { + cart.total -= discount; + } + ``` + +3. **Small surface area.** Fewer methods = fewer tests needed. Fewer params = simpler test setup. + +## Relationships + +- A **Module** has exactly one **Interface** (the surface it presents to callers and tests). +- **Depth** is a property of a **Module**, measured against its **Interface**. +- A **Seam** is where a **Module**'s **Interface** lives. +- An **Adapter** sits at a **Seam** and satisfies the **Interface**. +- **Depth** produces **Leverage** for callers and **Locality** for maintainers. + +## Rejected framings + +- **Depth as ratio of implementation-lines to interface-lines** (Ousterhout): rewards padding the implementation. We use depth-as-leverage instead. +- **"Interface" as the TypeScript `interface` keyword or a class's public methods**: too narrow — interface here includes every fact a caller must know. + +## Going deeper + +- **Deepening a cluster given its dependencies** — see [DEEPENING.md](DEEPENING.md): dependency categories, seam discipline, and replace-don't-layer testing. +- **Exploring alternative interfaces** — see [DESIGN-IT-TWICE.md](DESIGN-IT-TWICE.md): spin up parallel sub-agents to design the interface several radically different ways, then compare on depth, locality, and seam placement. diff --git a/modules/claude-code/skills/craft-skill/GLOSSARY.md b/modules/claude-code/skills/craft-skill/GLOSSARY.md new file mode 100644 index 0000000..26d756d --- /dev/null +++ b/modules/claude-code/skills/craft-skill/GLOSSARY.md @@ -0,0 +1,195 @@ +# Glossary — Building Great Skills + +The domain model for what makes a skill great. A skill exists to wrangle determinism out of a stochastic system; the root virtue is **Predictability**, and every term below is a lever on it. This is the disclosed reference for [`craft-skill`](SKILL.md). + +The terms are grouped by axis: **Invocation** (how a skill is reached), **Information Hierarchy** (how its content is arranged), **Steering** (how the agent's runtime behaviour is shaped), and **Pruning** (how it is kept lean). Each **failure mode** lives beside the lever that cures it, tagged _failure mode_. + +**Bold terms** in any definition are themselves defined in this glossary; find them by their heading. + +## Predictability + +The degree to which a skill makes the agent behave the same _way_ on every run — the same process, not the same output (a brainstorming skill should _predictably_ diverge; its tokens vary, its behaviour doesn't). The root virtue every other term serves — cost and maintainability are symptoms of it, not rivals. + +_Avoid_: consistency, reliability, robustness, output-determinism + +## Invocation + +How a skill is reached — and the two loads you pay for the choice. + +### Model-Invoked + +A skill that keeps its **description** field, so the agent can see it and fire it autonomously — and the human can still type its name, so model-invocation always _includes_ user reach. There is no model-only state: a description only ever _adds_ agent discovery, never removes the human's. Pays a permanent **context load** on every turn in exchange for that discoverability. Reachable by other skills, because the description that makes it agent-discoverable makes it invocable. A model-invoked skill whose content is all **reference** is also one home for shared reference: another skill can invoke it, so reference needed by several skills lives in one place. Pick model-invocation only when the agent must reach the skill on its own; if it never fires except by hand, drop the description and pay no context load. + +_Avoid_: ability, tool, capability + +### User-Invoked + +A skill with its **description** stripped — invisible to the agent and reachable only by the human typing its name (user-_only_, where **model-invoked** is user-_and-agent_). Trades agent-discoverability for zero **context load**. Because it has no description, nothing but the human can reach it: no other skill can fire it. + +_Avoid_: procedure, workflow, command + +### Description + +The skill's machine-readable trigger, and the one **context pointer** a **model-invoked** skill is forced to keep loaded at all times. Its mere presence _is_ the invocation axis: keep it and the skill is model-invoked (and reachable by other skills); delete it and the skill is **user-invoked**, reachable only by the human. The source of a model-invoked skill's **context load**. + +_Avoid_: frontmatter, summary + +### Context Pointer + +A reference held in the agent's context that names some out-of-context material and encodes the condition for reaching it. The **description** is the top-level context pointer (context window → skill); pointers to disclosed files are the same object one level down. Its wording, not the target, decides _when_ the agent reaches — and _how reliably_. A must-have target behind a weakly worded pointer is a variance bug: fix the wording first, and inline the material only if sharpening fails. + +_Avoid_: link, reference, import + +### Context Load + +The cost a **model-invoked** skill imposes on the agent's context window — its **description**, always loaded, spending both tokens and attention. What **user-invoked** skills escape by having no description, and the brake on splitting into more model-invoked skills. + +_Avoid_: token cost, context bloat + +### Cognitive Load + +The cost a **user-invoked** skill imposes on the human — what they must hold in their head: which skills exist and when to reach for each (the human is the index). What **model-invocation** removes by being agent-discoverable, and the brake on splitting into more user-invoked skills. Not a cost to minimise: it is the price of human agency, the reason some skills stay user-invoked. Spend it where human judgement matters; remove it where it does not. + +_Avoid_: human index, burden, overhead + +### Router Skill + +A **user-invoked** skill whose job is to point at your other user-invoked skills — naming each and when to reach for it — so the human has one skill to remember instead of many. It can only hint, never fire them: user-invoked skills have no **description**, so nothing but the human can reach them. The cure for **cognitive load** when user-invoked skills multiply. + +_Avoid_: dispatcher, menu, registry, index, router procedure + +### Granularity + +How finely you divide skills. Finer division spends one of the two loads: more **model-invoked** skills spend **context load** (more descriptions crowding the window and competing for attention); more **user-invoked** skills spend **cognitive load** (more for the human to remember and reach for). Two cuts guide the division. By **invocation**, split off a model-invoked skill where you have a distinct **leading word** to trigger it — a trigger word you actually use in your prompts. By **sequence**, split a run of **steps** where a step's **post-completion steps** need hiding, since isolating it in its own context clears what follows. Beware the reverse: merging sequences exposes each step's post-completion steps to what follows, inviting premature completion. + +_Avoid_: chunking, modularity + +## Information Hierarchy + +How a skill's content is arranged, and how far down the ladder each piece sits. + +### Information Hierarchy + +A skill's content ranked by how immediately the agent needs it — a single ladder, produced by two cuts: in-file or behind a pointer, and step or reference. The rungs: + +- **Steps** — in-file, primary +- **Reference**, in-file — secondary +- **Reference**, disclosed — behind a **context pointer** + +A skill with no **steps** uses just the bottom two rungs — often a legitimately flat peer-set (e.g. every rule of a review on one rung), which is a fine arrangement, not a smell. The hierarchy is independent of invocation: a skill can be model- or user-invoked whether it is all steps, all reference, or both. When a skill has steps, in-file reference that should be disclosed buries them and turns attending to them into a coin-flip — a variance lever, not just a legibility one. Keep the top of the ladder legible; push down it whatever you can. + +_Avoid_: structure, organization, layout + +### Steps + +The ordered actions the agent performs — when a skill has them, the primary tier of its content, and the part that earns its place in SKILL.md. Not every skill has steps: a skill can be all steps (`test-driven-development`), all **reference** (a review), or both, independent of invocation. Every step ends on a **completion criterion**, clear or vague. + +_Avoid_: workflow, instructions, choreography + +### Reference + +Material the agent refers to on demand — definitions, facts, parameters, examples, conditional instructions. When a skill has **steps** it is secondary to them; when a skill has none it is the entire content; or it lives outside any skill entirely — see **External Reference**. Reached via **context pointers**, and the prime candidate for **progressive disclosure**. + +_Avoid_: supporting material, docs, background + +### External Reference + +**Reference** that lives outside the skill system — a plain file, no **description**, no **steps**, not invocable — that any skill can point at. The home for shared reference that needn't fire on its own, and the only shared home two **user-invoked** skills can use, since neither has a description and so neither can fire the other. + +_Avoid_: doc, resource, knowledge base + +### Progressive Disclosure + +Moving **reference** down the ladder — out of SKILL.md and behind a **context pointer** — so the top stays legible. Not primarily a token optimisation; it is how the **information hierarchy** is protected. Licensed by **branching**: disclose what only some branches need, inline what every path needs, and if a pointer fires unreliably on must-have material, sharpen its wording, and pull it back inline only if that fails. + +_Avoid_: lazy loading, chunking + +### Co-location + +Keeping the material an agent needs at once in one place — a concept's definition, rules, and caveats under a single heading, not scattered across the file — so reading one part brings its neighbours with it. The within-file companion to the **Information Hierarchy**: the hierarchy ranks _how far down_ a piece sits; co-location decides _what sits beside it_ once there. There is no formula for the right format of a body of **reference**; the test is that a skill should read like documentation written for the agent, and grouped material reads that way where scattered material does not. Distinct from **Duplication**: that repeats one meaning in two places, where scattering fragments a single meaning across many. + +_Avoid_: grouping, clustering, cohesion + +### Sprawl + +_Failure mode._ A skill that is simply too long — too many lines in SKILL.md — independent of whether they are stale or repeated. Even an all-live, all-unique skill can sprawl. It costs readability (the agent wades through more before it can act, and attention thins across the excess), maintainability (every extra line is one more to keep **relevant**), and tokens. The cure is the **information hierarchy**: push **reference** down behind **context pointers**, and split by **branch** or sequence so each path carries only what it needs. Distinct from **sediment** (length from stale accumulation) and **duplication** (length from repeated meaning) — sprawl is length itself, whatever its cause. + +_Avoid_: bloat, length, size, verbosity + +## Steering + +The levers that shape the agent's runtime behaviour toward **Predictability**. + +### Branch + +A distinct way a skill can be invoked — a case the skill handles — so different runs take different paths through it. A skill with many steps may carry many branches; a linear one has none. + +_Avoid_: path, case, fork + +### Leading Word + +A compact concept — also called a _Leitwort_ — already living in the model's pretraining, that the agent thinks with while running the skill. It encodes a behavioural principle in the fewest possible tokens by invoking priors the model already holds (e.g. _lesson_, _proximal zone of development_, _fog of war_, _tracer bullets_). Repeated as a token, never as a sentence, it accumulates a distributed definition across the skill and anchors a whole region of behaviour. Coining your own works if you define it clearly, but a made-up word recruits no priors — you pay in definition tokens what a pretrained word gives free. Reach for an existing word first. + +A leading word serves **predictability** twice. In the body it anchors **execution** — the agent reaches for the same behaviour every time the concept appears, and inside flat reference it focuses attention on a class of thing to look for, recruiting the right checks each run. In the **description** it anchors **invocation** — and not only within the skill: when the same word lives in your prompts, your docs, and your codebase, the agent links that shared language to the skill and fires it more reliably. Word a description with the leading words you actually use when you want the skill. + +_Avoid_: keyword, term, motif + +### Completion Criterion + +The condition that tells the agent a unit of work is done — the target it judges against. Two properties make it a lever, not just a quality. Its **clarity** (can the agent tell done from not-done?) resists **premature completion** — a vague bound ("understanding reached") lets the agent declare done and slip to the next step; this axis needs _steps_ to bite, since premature completion is a between-steps failure. Its **demand** (how much it requires) sets **legwork** — "every modified model accounted for" forces thorough work where "produce a change list" does not — and this axis is _not_ step-bound: it can bind a body of flat reference too, which is how a skill with no steps still carries an exhaustiveness bar ("every rule applied"). The strongest criteria are both checkable and exhaustive. + +_Avoid_: done condition, exit condition, stopping rule + +### Legwork + +The work an agent does behind the scenes within a single step — reading files, exploring the codebase, making changes, digging up what it needs rather than offloading to the user. It lives below the step structure: never written as its own step, latent in the wording, controlled by the agent rather than the skill. The within-step counterpart to **post-completion steps**' across-step pull. Raised by a **leading word** (_comprehensive_, _thorough_) or a **completion criterion** that demands the work be exhaustive — including the demand axis applied to flat reference, which is what drives a skill of flat reference to cover all its rungs. Goes thin either when that demand is missing or when **premature completion** cuts the step short. + +_Avoid_: scope, effort, diligence, coverage + +### Post-Completion Steps + +The **steps** that follow the current step. Visible, they pull the agent forward into **premature completion** — the more it sees, the stronger the tug; the defence is to hide them by splitting the sequence of steps into two. + +_Avoid_: horizon, fog of war, lookahead + +### Premature Completion + +_Failure mode._ Ending the current step before it is genuinely done, because the agent's attention slips to being done rather than to the work. A between-steps failure: it needs **steps** to occur — a skill with no steps that quits early isn't premature completion but thin **legwork** under an unmet demand. A tug-of-war between two forces: visible **post-completion steps** (the pull forward) and the **completion criterion**'s clarity (the resistance — a sharp, checkable bar holds; a vague one gives way). Fuzziness is the necessary condition: a sharp bound resists the pull no matter how many later steps are visible, so a step that never rushes needs no defending. Two levers hold a step that does, but reach for them in order: **sharpen the bound first** — it is local and cheap. Only when the criterion is irreducibly fuzzy _and_ you actually observe the rush do you **hide the later steps** — and hiding only works across a real context boundary (a user-invoked hand-off or a subagent dispatch; an inline model-invoked call leaves the later steps in context and clears nothing). One cause of thin legwork, but distinct from it: legwork can be thin even when a step runs to full completion. + +_Avoid_: premature closure, the rush, rushing, shortcutting + +## Pruning + +Keeping a skill lean — each remedy paired with the failure it cures. + +### Single Source of Truth + +The desired state where each meaning lives in exactly one authoritative place, so a change to the skill's behaviour is a change in one place. **Duplication** is its violation. + +_Avoid_: home, canonical location + +### Duplication + +_Failure mode._ The same meaning given more than one **single source of truth**. It costs maintenance (change one place, you must change the others), costs tokens, and inflates prominence — repeating a meaning weights it on the ladder past its real rank. The accidental inverse of a **leading word**, which raises attention on purpose by repeating a token, never the meaning. + +_Avoid_: repetition, redundancy + +### Relevance + +Whether a line still bears on what the skill does — the lens for what to keep. A line loses relevance either by never bearing on the task (mere exposition, or a **branch** that should be disclosed) or by going stale: drifting out of date as the behaviour or world it describes changes. Shorter skills are easier to keep relevant, because each line is cheaper to check. Distinct from **no-op**: relevance asks whether a line bears on the task, not whether it changes behaviour. + +_Avoid_: load-bearing, staleness, freshness + +### Sediment + +_Failure mode._ Layers of old content that settle in a skill and are never cleared, because adding feels safe and removing feels risky — so stale and irrelevant lines accumulate and you must core down through them to find what is still live. The default fate of any skill without a pruning discipline; the slow erosion of **relevance**, as opposed to **duplication**'s repeated meaning. + +_Avoid_: accretion, bloat, cruft, rot + +### No-Op + +_Failure mode._ An instruction that changes nothing because the model already does it by default — you pay load to tell the agent what it would do anyway. The test: does a line change behaviour versus the default? A line can be perfectly **relevant** and still be a no-op. The same priors that make a **leading word** free make a no-op worthless. + +A leading word is a _technique_; No-Op is a _verdict_ on a line — and they cross. A leading word too weak to beat the default is a no-op (_be thorough_ when the agent is already thorough-ish), and the fix is a stronger word that passes the verdict (_relentless_), not a different technique. So the No-Op test — does it change behaviour versus the default? — is also how you grade whether a leading word is earning its repetitions. This is model-relative, not reader-relative: two people disagreeing over whether a line is a no-op disagree about the default, and settle it by running the skill, not by debate. + +_Avoid_: redundant instruction, restating the obvious, belaboring diff --git a/modules/claude-code/skills/craft-skill/SKILL.md b/modules/claude-code/skills/craft-skill/SKILL.md new file mode 100644 index 0000000..7b7f78c --- /dev/null +++ b/modules/claude-code/skills/craft-skill/SKILL.md @@ -0,0 +1,49 @@ +--- +name: craft-skill +description: Draft a new skill, or audit and rewrite an existing one, judged against the vocabulary in GLOSSARY.md. +disable-model-invocation: true +--- + +Draft a new skill from scratch, or audit and rewrite an existing one — both judged against one bar: **predictability**, the agent taking the same process every run. **Bold terms** are defined in [`GLOSSARY.md`](GLOSSARY.md); look them up there for the full meaning. + +This skill does not judge general prose quality (clarity, jargon, sentence structure) — only skill-specific structure. A dedicated technical-writing-guide skill will cover the former once it exists; until then, use your own judgment for sentence-level prose. + +## Which branch + +If the request describes a new workflow, capability, or repeated manual process with no existing skill named — **Draft a new skill**. If it names an existing skill (by name or path) to review, fix, or improve — **Audit an existing skill**. Both end at **Verify and ship**. + +## Draft a new skill + +1. **Capture intent.** If the conversation already contains the workflow (e.g. "turn this into a skill"), extract answers from it first; only ask about what's missing. Ask one question at a time — several at once is bewildering: + - What should this make the agent do — is it a **procedure** (ordered **steps**), **knowledge** it consults (**reference**), or both? This decides the shape from the start. + - When would you actually reach for it: do you type its name, or should the agent reach for it unprompted? Walk the **context load** vs **cognitive load** tradeoff explicitly rather than defaulting — see `Invocation` in GLOSSARY.md. + - Does it have distinct **branches** — cases that take different paths? Name each. A linear checklist's items aren't branches by themselves — look for actual alternate paths, not the steps that always all run. + - Is this workflow already documented somewhere in the project (a README, CLAUDE.md, CONTRIBUTING)? If so the draft should point there rather than restate it — see `External Reference` and `Single Source of Truth` in GLOSSARY.md. + - For each step, what does done look like — a **completion criterion** you could check without ambiguity? + - Is there already a word — in your prompts, docs, or codebase — that names this behavior? Reach for that **leading word** before coining one. + Done when every axis above has an answer, or the user says to just draft something and iterate. + +2. **Write the draft.** First decide where it lives: project-local `.claude/skills/` if the workflow is tied to this one repo, `~/.claude/skills/` if it's general-purpose across projects. Then follow the **information hierarchy**: steps for what the agent does in order, in-file **reference** for facts every branch needs, and disclose the rest behind a pointer — to a sibling file, or to the existing project docs identified in step 1 rather than restating them. Done when every branch from step 1 has somewhere to live, and no sentence fails the no-op test in isolation (see `No-Op` in GLOSSARY.md). + +## Audit an existing skill + +1. **Locate it.** Check the current project's `.claude/skills/`, then `~/.claude/skills/`, then `~/.claude/skills/library/`, in that order; ask if the name is ambiguous across locations. If it's tracked in a project's `skills-lock.yaml`, mention that editing it here will make it read as locally-customized to `update-skills` — confirm that's actually intended rather than editing the library source. + +2. **Apply the checklist.** Read the skill and its disclosed files, then check each against GLOSSARY.md, quoting the offending line for anything that fails: + - **Premature completion** — is each completion criterion checkable, and does it demand what the step actually needs? + - **Duplication** — does any meaning appear in more than one place? + - **Sediment** — any line that no longer bears on what the skill does? + - **Sprawl** — could in-file reference be disclosed instead, or a run of steps split by branch? + - **No-op** — any sentence the model would already do by default? Test sentence by sentence, not line by line — a line can carry one load-bearing sentence and one no-op sentence together. + - Is the **invocation** choice (model- vs user-invoked) still the right one for how this skill actually gets used? Is there a restated concept that should collapse into a **leading word**? + +3. **Rewrite** based on the findings. Done when every finding from step 2 is either addressed or explicitly noted as intentionally kept. + +## Verify and ship + +1. Propose one realistic test prompt — reflecting the trigger phrasing gathered (draft) or the skill's existing purpose (audit) — and get it confirmed or adjusted before spending a run on it. +2. Spawn one subagent: give it the skill's path and the confirmed prompt, have it attempt the task using the skill, and report back what happened — including anywhere it hesitated, misread the skill, or did something unexpected. +3. Re-read the draft/rewrite against GLOSSARY.md's failure modes in light of that run, and fix whatever either pass turned up. If the fix is substantial, repeat from step 1; otherwise it's done. +4. Stage the specific changed or created paths — one path per file, never a wildcard — with the host project's own staging convention: plain `git add ` normally, or e.g. `dot add ` in this dotfiles setup (wrap as `fish -c "dot add "` if the invoking shell isn't fish — `dot` is a fish function, not a binary on `$PATH`). Do not commit; that's left to the user. + +Done when the subagent's run succeeded without confusion on the confirmed prompt, the checklist raised nothing outstanding, and every changed path is staged. diff --git a/modules/claude-code/skills/domain-modeling/ADR-FORMAT.md b/modules/claude-code/skills/domain-modeling/ADR-FORMAT.md new file mode 100644 index 0000000..15c134e --- /dev/null +++ b/modules/claude-code/skills/domain-modeling/ADR-FORMAT.md @@ -0,0 +1,47 @@ +# ADR Format + +ADRs live in `.claude/adr/` and use sequential numbering: `0001-slug.md`, `0002-slug.md`, etc. + +Create the `.claude/adr/` directory lazily — only when the first ADR is needed. + +## Template + +```md +# {Short title of the decision} + +{1-3 sentences: what's the context, what did we decide, and why.} +``` + +That's it. An ADR can be a single paragraph. The value is in recording *that* a decision was made and *why* — not in filling out sections. + +## Optional sections + +Only include these when they add genuine value. Most ADRs won't need them. + +- **Status** frontmatter (`proposed | accepted | deprecated | superseded by ADR-NNNN`) — useful when decisions are revisited +- **Considered Options** — only when the rejected alternatives are worth remembering +- **Consequences** — only when non-obvious downstream effects need to be called out + +## Numbering + +Scan `.claude/adr/` for the highest existing number and increment by one. + +## When to offer an ADR + +All three of these must be true: + +1. **Hard to reverse** — the cost of changing your mind later is meaningful +2. **Surprising without context** — a future reader will look at the code and wonder "why on earth did they do it this way?" +3. **The result of a real trade-off** — there were genuine alternatives and you picked one for specific reasons + +If a decision is easy to reverse, skip it — you'll just reverse it. If it's not surprising, nobody will wonder why. If there was no real alternative, there's nothing to record beyond "we did the obvious thing." + +### What qualifies + +- **Architectural shape.** "We're using a monorepo." "The write model is event-sourced, the read model is projected into Postgres." +- **Integration patterns between contexts.** "Ordering and Billing communicate via domain events, not synchronous HTTP." +- **Technology choices that carry lock-in.** Database, message bus, auth provider, deployment target. Not every library — just the ones that would take a quarter to swap out. +- **Boundary and scope decisions.** "Customer data is owned by the Customer context; other contexts reference it by ID only." The explicit no-s are as valuable as the yes-s. +- **Deliberate deviations from the obvious path.** "We're using manual SQL instead of an ORM because X." Anything where a reasonable reader would assume the opposite. These stop the next engineer from "fixing" something that was deliberate. +- **Constraints not visible in the code.** "We can't use AWS because of compliance requirements." "Response times must be under 200ms because of the partner API contract." +- **Rejected alternatives when the rejection is non-obvious.** If you considered GraphQL and picked REST for subtle reasons, record it — otherwise someone will suggest GraphQL again in six months. diff --git a/modules/claude-code/skills/domain-modeling/CONTEXT-FORMAT.md b/modules/claude-code/skills/domain-modeling/CONTEXT-FORMAT.md new file mode 100644 index 0000000..f256d20 --- /dev/null +++ b/modules/claude-code/skills/domain-modeling/CONTEXT-FORMAT.md @@ -0,0 +1,30 @@ +# CONTEXT.md Format + +## Structure + +```md +# {Context Name} + +{One or two sentence description of what this context is and why it exists.} + +## Language + +**Order**: +{A one or two sentence description of the term} +_Avoid_: Purchase, transaction + +**Invoice**: +A request for payment sent to a customer after delivery. +_Avoid_: Bill, payment request + +**Customer**: +A person or organization that places orders. +_Avoid_: Client, buyer, account +``` + +## Rules + +- **Be opinionated.** When multiple words exist for the same concept, pick the best one and list the others under `_Avoid_`. +- **Keep definitions tight.** One or two sentences max. Define what it IS, not what it does. +- **Only include terms specific to this project's context.** General programming concepts (timeouts, error types, utility patterns) don't belong even if the project uses them extensively. Before adding a term, ask: is this a concept unique to this context, or a general programming concept? Only the former belongs. +- **Group terms under subheadings** when natural clusters emerge. If all terms belong to a single cohesive area, a flat list is fine. diff --git a/modules/claude-code/skills/domain-modeling/SKILL.md b/modules/claude-code/skills/domain-modeling/SKILL.md new file mode 100644 index 0000000..e2a2b9e --- /dev/null +++ b/modules/claude-code/skills/domain-modeling/SKILL.md @@ -0,0 +1,56 @@ +--- +name: domain-modeling +description: Build and sharpen a project's domain model. Use when the user wants to pin down domain terminology or a ubiquitous language, record an architectural decision, or when another skill needs to maintain the domain model. +--- + +# Domain Modeling + +Actively build and sharpen the project's domain model as you design. This is the *active* discipline — challenging terms, inventing edge-case scenarios, and writing the glossary and decisions down the moment they crystallise. (Merely *reading* `.claude/CONTEXT.md` for vocabulary is not this skill — that's a one-line habit any skill can do. This skill is for when you're changing the model, not just consuming it.) + +## File structure + +``` +/ +├── .claude/ +│ ├── CONTEXT.md +│ └── adr/ +│ ├── 0001-event-sourced-orders.md +│ └── 0002-postgres-for-write-model.md +└── src/ +``` + +Create files lazily — only when you have something to write. If no `.claude/CONTEXT.md` exists, create it when the first term is resolved. If no `.claude/adr/` exists, create it when the first ADR is needed. + +## During the session + +### Challenge against the glossary + +When the user uses a term that conflicts with the existing language in `.claude/CONTEXT.md`, call it out immediately. "Your glossary defines 'cancellation' as X, but you seem to mean Y — which is it?" + +### Sharpen fuzzy language + +When the user uses vague or overloaded terms, propose a precise canonical term. "You're saying 'account' — do you mean the Customer or the User? Those are different things." + +### Discuss concrete scenarios + +When domain relationships are being discussed, stress-test them with specific scenarios. Invent scenarios that probe edge cases and force the user to be precise about the boundaries between concepts. + +### Cross-reference with code + +When the user states how something works, check whether the code agrees. If you find a contradiction, surface it: "Your code cancels entire Orders, but you just said partial cancellation is possible — which is right?" + +### Update .claude/CONTEXT.md inline + +When a term is resolved, update `.claude/CONTEXT.md` right there. Don't batch these up — capture them as they happen. Use the format in [CONTEXT-FORMAT.md](./CONTEXT-FORMAT.md). + +`.claude/CONTEXT.md` should be totally devoid of implementation details. Do not treat `.claude/CONTEXT.md` as a spec, a scratch pad, or a repository for implementation decisions. It is a glossary and nothing else. + +### Offer ADRs sparingly + +Only offer to create an ADR when all three are true: + +1. **Hard to reverse** — the cost of changing your mind later is meaningful +2. **Surprising without context** — a future reader will wonder "why did they do it this way?" +3. **The result of a real trade-off** — there were genuine alternatives and you picked one for specific reasons + +If any of the three is missing, skip the ADR. Use the format in [ADR-FORMAT.md](./ADR-FORMAT.md). diff --git a/modules/claude-code/skills/gitea-axi/SKILL.md b/modules/claude-code/skills/gitea-axi/SKILL.md new file mode 100644 index 0000000..5e30a48 --- /dev/null +++ b/modules/claude-code/skills/gitea-axi/SKILL.md @@ -0,0 +1,48 @@ +--- +name: gitea-axi +description: Use when working with a Gitea repository's issues, pull requests, labels, reviews, comments, or milestones — listing, viewing, creating, editing, commenting, reviewing, or merging on a Gitea host such as git.alexion.dev. Prefer this over the `tea` CLI, raw Gitea API calls, or improvised `git` commands for issue/PR/label work. +--- + +# gitea-axi + +`gitea-axi` is an agent-ergonomic CLI for a Gitea repository's issues and pull requests. +Its output is compact TOON built for another program to read, and its errors are structured with actionable suggestions. + +## When to use it + +Reach for `gitea-axi` whenever a task touches a Gitea repository's issues, pull requests, labels, or reviews. + +- **Over `tea`:** `gitea-axi` returns structured output and typed errors instead of human-formatted tables, and it defaults the repository and login from the local checkout. +- **Over raw Gitea API calls:** it handles auth, pagination, name-to-ID resolution, and review-decision aggregation for you, so you do not hand-roll HTTP. +- **Over improvised `git`:** for anything about issues or pull requests as entities (state, reviews, labels, comments) rather than local commits and branches. + +## Targeting and authentication + +Every command resolves two things: which repository to act on, and which credentials to authenticate with. +Get both right on the first call — they are the usual reason a command fails and has to be retried. + +- **Repository.** Inside a Gitea checkout it is taken from the `origin` remote automatically. + Outside a checkout you must name it: pass `-R OWNER/NAME` on every command (or set `GITEA_AXI_REPO=OWNER/NAME` once for the session). +- **Credentials.** When the environment is pre-configured — `GITEA_AXI_TOKEN` together with `GITEA_AXI_API_URL` — authentication is automatic and you need nothing more. + Otherwise credentials come from a `tea` login: pass `--login ` (or set `GITEA_AXI_LOGIN=`) unless the checkout's remote already selects one. + +So outside a checkout with the token in the environment, `gitea-axi -R OWNER/NAME …` is all you need; do not go hunting for a config file or a login profile. + +## Command groups + +- `issue` — list, view, create, comment on, edit, close/reopen, pin, and link issues. +- `pr` — create, view, comment on, edit, review, merge, check out, diff, and inspect the checks of pull requests. +- `label` — list, create, edit, and delete labels. +- `search` — full-text search; it takes a subcommand, so search issues with `search issues ""` and pull requests with `search prs ""` (a bare `search ""` is not valid). +- `setup` — install this skill (`setup`) and, opt-in, the SessionStart dashboard hook (`setup hooks`). + +To read one issue's fields, reach straight for `issue view `: it shows labels and state by default, and takes `--fields assignees,milestone,…` for the rest. +You rarely need `issue list` to answer a question about a single issue. + +## Discovery + +This skill is a pointer, not a command reference — the CLI is the single source of truth for its own interface. + +- Run `gitea-axi` with no arguments for the repository dashboard (open issues and pull requests). + Add `--full` for the open-PR table and issue counts by label. +- Run `gitea-axi --help` (or `gitea-axi --help`) for the exact flags of any command. diff --git a/modules/claude-code/skills/grill/SKILL.md b/modules/claude-code/skills/grill/SKILL.md new file mode 100644 index 0000000..7431d71 --- /dev/null +++ b/modules/claude-code/skills/grill/SKILL.md @@ -0,0 +1,20 @@ +--- +name: grill +description: Interview the user relentlessly about a plan or design, capturing the resolved terms and decisions into the project's domain model as you go if one exists. Use when the user wants to stress-test a plan before building, or uses any 'grill' trigger phrase. +--- + +Interview me relentlessly about every aspect of this plan or design. Walk down each branch of the design tree, resolving dependencies between decisions one by one, and give your recommended answer for each question. Keep going until every branch carries an explicit decision and no dependency between decisions is left open — not merely until it feels like "we understand each other." + +Ask the questions one at a time, waiting for feedback on each before continuing. Asking several at once is bewildering. + +If a question can be answered by exploring the codebase, explore the codebase instead of asking it. + +**Never start implementation during or after the interview without an explicit instruction from the user.** This applies at every point — mid-interview and after the final question alike. + +## Closing the interview + +When every branch carries an explicit decision and no dependency is left open, produce a concise summary of all decisions reached, then stop and wait for the user's next instruction. + +## Tracking the domain model as you go + +If a `.claude/CONTEXT.md` file exists in the project, also run [`domain-modeling`](../domain-modeling/SKILL.md) alongside this interview: resolve each term into `.claude/CONTEXT.md` the moment it crystallizes, and offer an ADR using that skill's own criteria — hard to reverse, surprising without context, and the result of a real trade-off. If no `.claude/CONTEXT.md` exists, run the interview alone with no doc side effects. diff --git a/modules/claude-code/skills/implement/SKILL.md b/modules/claude-code/skills/implement/SKILL.md new file mode 100644 index 0000000..6b27cbf --- /dev/null +++ b/modules/claude-code/skills/implement/SKILL.md @@ -0,0 +1,72 @@ +--- +name: implement +description: Implement a task file produced by /to-tasks on its own branch, review it, close it out, and open a PR. +disable-model-invocation: true +--- + +Implement a task file end-to-end: branch, build it, review it, close it out, and open a PR. + +## Process + +### 1. Read the task file and check blockers + +The user passes the path to a task file (`.claude/tasks/-slug.md`, as produced by `/to-tasks`) explicitly — don't infer one from context. + +If the task's frontmatter has a `blocked-by` field, read each referenced task file and check for any unresolved `- [ ]` acceptance criterion. If any blocker isn't fully resolved, warn the user which one and why, and confirm before proceeding — don't refuse outright. + +### 2. Sync `main` and branch off it + +Switch to `main`, fast-forward it (`git pull --ff-only`), then create and switch to a branch named `task--` — taken verbatim from the task file's basename, so `.claude/tasks/0003-issue-view-and-truncation.md` gives `task-0003-issue-view-and-truncation`. +Use whatever git invocation the project itself uses; a repo may wrap it. + +Stop and ask the user before going further if: + +- **The working tree has uncommitted changes.** Never stash them automatically. +- **`git pull --ff-only` fails.** Local `main` has diverged; report what diverged. Never `reset --hard`. +- **The task's `blocked-by` work isn't reachable from `main`.** The blocker's PR is likely unmerged; name it. + +If the task branch already exists, switch to it and carry on — don't recreate it, and don't rebase it onto the freshly pulled `main`. +Always branch off `main`, never off a sibling task branch. + +### 3. Implement + +Build the work described in the task's "What to build" section, satisfying its acceptance criteria. Use `/test-driven-development` where possible, at the seams already agreed when the spec or task was written. + +Run typechecking regularly, single test files regularly, and the full test suite once at the end. + +### 4. Stage the changes + +Stage (`git add`) each file you create or modify, specifically — not `git add -A` — so nothing untracked and unrelated gets swept in. + +### 5. Review + +Run `/review-uncommitted`, passing the task file itself as the spec source — it already links back to its parent spec via its `spec` frontmatter field, if any. Address anything it raises before moving on. + +Keep its report — step 7 puts part of it in the PR. + +### 6. Close out the task file + +Mark every acceptance criterion `[x]` if satisfied or `[-]` if deliberately dropped, so none are left `[ ]`. Append a `## Implementation Notes` section explaining any deviations from the plan — dropped criteria (referencing which, and why), scope changes, decisions made mid-implementation, follow-ups worth flagging. Skip the section only if nothing deviated. Leave the `spec` and `blocked-by` frontmatter fields untouched — they're a permanent record, not a checklist to clear (see `to-tasks`'s `TASK-FORMAT.md`). + +Stage the updated task file with the rest. + +### 7. Commit, push, and open a PR + +Make **one** commit for the whole task, code and task file together. +Match the repo's existing commit convention — read its recent history or its CLAUDE.md, don't assume one — and reference the task in the subject, e.g. `(task 0003)`. + +Push the branch (`git push -u origin task--`) and open a pull request against `main` with the repo's forge CLI: `tea` for Gitea, `gh` for GitHub. +Never base the PR on a sibling task branch. +Open it ready, not draft. + +The PR body carries: + +- The task file's path. +- A short summary of what was built, and any deviations — the same ones just written into `## Implementation Notes`. +- A `## Review` section: the `## Risk` block from step 5 verbatim (overall rating plus its six factor lines), then **only** the Standards and Spec findings left unaddressed, each with a one-line reason. Findings that were fixed are already in the diff; leave them out. + +Don't ask for confirmation before pushing or opening the PR. +If the repo has no remote, stop after the commit and report that no PR was opened. + +Stay on the task branch when done. +Report the branch name, the PR URL, and any unaddressed review findings. diff --git a/modules/claude-code/skills/improve-codebase/HTML-REPORT.md b/modules/claude-code/skills/improve-codebase/HTML-REPORT.md new file mode 100644 index 0000000..731bd96 --- /dev/null +++ b/modules/claude-code/skills/improve-codebase/HTML-REPORT.md @@ -0,0 +1,119 @@ +# HTML Report Format + +The architectural review is rendered as a single self-contained HTML file in the OS temp directory. Tailwind and Mermaid both come from CDNs. Mermaid handles graph-shaped diagrams reliably; hand-built divs and inline SVG handle the more editorial visuals (mass diagrams, cross-sections). Mix the two — don't lean on Mermaid for everything, it'll start to look generic. + +## Scaffold + +```html + + + + + Architecture review — {{repository name}} + + + + + +
+
...
+
...
+
...
+
+ + +``` + +## Header + +Repo name, date, and a compact legend: solid box = module, dashed line = seam, red arrow = leakage, thick dark box = deep module. No introduction paragraph — straight into the candidates. + +## Candidate card + +The diagrams carry the weight. Prose is sparse, plain, and uses the glossary terms (from the `/codebase-design` skill) without ceremony. + +Each candidate is one `
`: + +- **Title** — short, names the deepening (e.g. "Collapse the Order intake pipeline"). +- **Badge row** — recommendation strength (`Strong` = emerald, `Worth exploring` = amber, `Speculative` = slate), plus a tag for the dependency category (`in-process`, `local-substitutable`, `ports & adapters`, `mock`). +- **Files** — monospaced list, `font-mono text-sm`. +- **Before / After diagram** — the centrepiece. Two columns, side by side. See patterns below. +- **Problem** — one sentence. What hurts. +- **Solution** — one sentence. What changes. +- **Wins** — bullets, ≤6 words each. e.g. "Tests hit one interface", "Pricing logic stops leaking", "Delete 4 shallow wrappers". +- **ADR callout** (if applicable) — one line in an amber-tinted box. + +No paragraphs of explanation. If the diagram needs a paragraph to be understood, redraw the diagram. + +## Diagram patterns + +Pick the pattern that fits the candidate. Mix them. Don't make every diagram look the same — variety is part of the point. + +### Mermaid graph (the workhorse for dependencies / call flow) + +Use a Mermaid `flowchart` or `graph` when the point is "X calls Y calls Z, and look at the mess." Wrap it in a Tailwind-styled card so it doesn't feel parachuted in. Style with classDef to colour leakage edges red and the deep module dark. Sequence diagrams work well for "before: 6 round-trips; after: 1." + +```html +
+
+    flowchart LR
+      A[OrderHandler] --> B[OrderValidator]
+      B --> C[OrderRepo]
+      C -.leak.-> D[PricingClient]
+      classDef leak stroke:#dc2626,stroke-width:2px;
+      class C,D leak
+  
+
+``` + +### Hand-built boxes-and-arrows (when Mermaid's layout fights you) + +Modules as `
`s with borders and labels. Arrows as inline SVG `` or `` elements positioned absolutely over a relative container. Reach for this when you want the "after" diagram to feel like one thick-bordered deep module with greyed-out internals — Mermaid won't render that with the right weight. + +### Cross-section (good for layered shallowness) + +Stack horizontal bands (`h-12 border-l-4`) to show layers a call passes through. Before: 6 thin layers each doing nothing. After: 1 thick band labelled with the consolidated responsibility. + +### Mass diagram (good for "interface as wide as implementation") + +Two rectangles per module — one for interface surface area, one for implementation. Before: interface rectangle is nearly as tall as the implementation rectangle (shallow). After: interface rectangle is short, implementation rectangle is tall (deep). + +### Call-graph collapse + +Before: a tree of function calls rendered as nested boxes. After: the same tree collapsed into one box, with the now-internal calls shown faded inside it. + +## Style guidance + +- Lean editorial, not corporate-dashboard. Generous whitespace. Serif optional for headings (`font-serif` works well with stone/slate). +- Colour sparingly: one accent (emerald or indigo) plus red for leakage and amber for warnings. +- Keep diagrams ~320px tall so before/after sits comfortably side by side without scrolling. +- Use `text-xs uppercase tracking-wider` for module labels inside diagrams — they should read as schematic, not as UI. +- The only scripts are the Tailwind CDN and the Mermaid ESM import. The report is otherwise static — no app code, no interactivity beyond Mermaid's own rendering. + +## Top recommendation section + +One larger card. Candidate name, one sentence on why, anchor link to its card. That's it. + +## Tone + +Plain English, concise — but the architectural nouns and verbs come straight from the `/codebase-design` glossary, terms and exclusions alike. Concision is not an excuse to drift. + +**Phrasings that fit the style:** + +- "Order intake module is shallow — interface nearly matches the implementation." +- "Pricing leaks across the seam." +- "Deepen: one interface, one place to test." +- "Two adapters justify the seam: HTTP in prod, in-memory in tests." + +**Wins bullets** name the gain in glossary terms: *"locality: bugs concentrate in one module"*, *"leverage: one interface, N call sites"*, *"interface shrinks; implementation absorbs the wrappers"*. Don't write *"easier to maintain"* or *"cleaner code"* — those terms aren't in the glossary and don't earn their place. + +No hedging, no throat-clearing, no "it's worth noting that…". If a sentence could be a bullet, make it a bullet. If a bullet could be cut, cut it. If a term isn't in the `/codebase-design` glossary, reach for one that is before inventing a new one. diff --git a/modules/claude-code/skills/improve-codebase/SKILL.md b/modules/claude-code/skills/improve-codebase/SKILL.md new file mode 100644 index 0000000..7e58837 --- /dev/null +++ b/modules/claude-code/skills/improve-codebase/SKILL.md @@ -0,0 +1,68 @@ +--- +name: improve-codebase +description: Scan a codebase for deepening opportunities, present them as a visual HTML report, then grill through whichever one you pick. +disable-model-invocation: true +--- + +# Improve Codebase + +Surface architectural friction and propose **deepening opportunities** — refactors that turn shallow modules into deep ones. The aim is testability and AI-navigability. + +This command is _informed_ by the project's domain model and built on a shared design vocabulary: + +- Run the `/codebase-design` skill for the architecture vocabulary (**module**, **interface**, **depth**, **seam**, **adapter**, **leverage**, **locality**) and its principles (the deletion test, "the interface is the test surface", "one adapter = hypothetical seam, two = real"). Use its terms exactly in every suggestion, per its glossary. +- The domain language in `.claude/CONTEXT.md` gives names to good seams; ADRs in `.claude/adr/` record decisions this command should not re-litigate. + +## Process + +### 1. Explore + +Read the project's domain glossary (`.claude/CONTEXT.md`) and any ADRs in the area you're touching first. + +Then use the Agent tool with `subagent_type=Explore` to walk every top-level module or directory in scope (the whole repository, or the area the user pointed you to) — even if only briefly for the ones that turn out clean. Within each, judge friction organically rather than against a rigid checklist: + +- Where does understanding one concept require bouncing between many small modules? +- Where are modules **shallow** — interface nearly as complex as the implementation? +- Where have pure functions been extracted just for testability, but the real bugs hide in how they're called (no **locality**)? +- Where do tightly-coupled modules leak across their seams? +- Which parts of the codebase are untested, or hard to test through their current interface? + +Apply the **deletion test** to anything you suspect is shallow: would deleting it concentrate complexity, or just move it? A "yes, concentrates" is the signal you want. + +Zero candidates is a legitimate outcome for a genuinely clean area — but it has to follow from having looked, not from stopping early. + +### 2. Present candidates as an HTML report + +Write a self-contained HTML file to the OS temp directory so nothing lands in the repository. Resolve the temp dir from `$TMPDIR`, falling back to `/tmp` (or `%TEMP%` on Windows), and write to `/architecture-review-.html` so each run gets a fresh file. Open it for the user — `xdg-open ` on Linux, `open ` on macOS, `start ` on Windows. Treat the open as best-effort: it's a no-op in a headless/sandboxed environment with no display server, so report the absolute path regardless of whether the open succeeded. + +The report uses **Tailwind via CDN** for layout and styling, and **Mermaid via CDN** for diagrams where a graph/flow/sequence reliably communicates the structure. Mix Mermaid with hand-crafted CSS/SVG visuals — use Mermaid when relationships are graph-shaped (call graphs, dependencies, sequences), and hand-built divs/SVG when you want something more editorial (mass diagrams, cross-sections, collapse animations). Each candidate gets a **before/after visualisation**. Be visual. + +For each candidate, render a card with: + +- **Files** — which files/modules are involved +- **Problem** — why the current architecture is causing friction +- **Solution** — plain English description of what would change +- **Benefits** — explained in terms of locality and leverage, and how tests would improve +- **Before / After diagram** — side-by-side, custom-drawn, illustrating the shallowness and the deepening +- **Recommendation strength** — one of `Strong`, `Worth exploring`, `Speculative`, rendered as a badge + +End the report with a **Top recommendation** section: which candidate you'd tackle first and why. + +**Use `.claude/CONTEXT.md` vocabulary for the domain, and the `/codebase-design` vocabulary for the architecture.** If `.claude/CONTEXT.md` defines "Order," talk about "the Order intake module" — not "the FooBarHandler," and not "the Order service." + +**ADR conflicts**: if a candidate contradicts an existing ADR, only surface it when the friction is real enough to warrant revisiting the ADR. Mark it clearly in the card (e.g. a warning callout: _"contradicts ADR-0007 — but worth reopening because…"_). Don't list every theoretical refactor an ADR forbids. + +See [HTML-REPORT.md](HTML-REPORT.md) for the full HTML scaffold, diagram patterns, and styling guidance. + +Do NOT propose interfaces yet. After the file is written, ask the user: "Which of these would you like to explore?" + +### 3. Grilling loop + +Once the user picks a candidate, run `/grill` to walk the design tree with them — constraints, dependencies, the shape of the deepened module, what sits behind the seam, what tests survive. + +Side effects happen inline as decisions crystallize — run `/domain-modeling` to keep the domain model current as you go, even if `.claude/CONTEXT.md` doesn't exist yet: + +- **Naming a deepened module after a concept not in `.claude/CONTEXT.md`?** Add the term to `.claude/CONTEXT.md`. Create the file lazily if it doesn't exist. +- **Sharpening a fuzzy term during the conversation?** Update `.claude/CONTEXT.md` right there. +- **User rejects the candidate with a load-bearing reason?** Offer an ADR, framed as: _"Want me to record this as an ADR so future architecture reviews don't re-suggest it?"_ Only offer when the reason would actually be needed by a future explorer to avoid re-suggesting the same thing — skip ephemeral reasons ("not worth it right now") and self-evident ones. +- **Want to explore alternative interfaces for the deepened module?** Run the `/codebase-design` skill and use its design-it-twice parallel sub-agent pattern. diff --git a/modules/claude-code/skills/library/nbdev/SKILL.md b/modules/claude-code/skills/library/nbdev/SKILL.md new file mode 100644 index 0000000..3aeeb3a --- /dev/null +++ b/modules/claude-code/skills/library/nbdev/SKILL.md @@ -0,0 +1,152 @@ +--- +name: nbdev +description: nbdev conventions for notebooks — directives, cell structure, docments, tests, execution. Use for any .ipynb operation — including reads — in an nbdev project. +--- + +# nbdev + +## Tool Preference + +- Use the **Jupyter MCP** for all `.ipynb` operations — read, edit, insert, delete, execute +- Do **not** use the built-in `NotebookEdit` tool; it writes cell source as a single JSON string which breaks standard Jupyter formatting and produces noisy diffs +- Re-read the notebook before editing if it may have changed since your last read — cell indices/IDs can shift under concurrent edits (e.g. via JupyterLab's real-time collaboration), and editing by a stale index can hit the wrong cell + +## nbdev Directives + +Directives are comments at the top of a cell that control how nbdev processes it: + +- `#| export` — include this cell in the exported Python module and in the docs +- `#| hide` — exclude this cell from both the module and the docs +- `#| hide_input` — show cell output in docs but hide the source code +- `#| default_exp module_name` — set which module this notebook exports to (second cell) +- `#| exporti` — export to module but do not show in docs (for internal helpers) +- `#| eval: false` — include in docs but do not execute during `nbdev-test` + +Imports needed only for tests or examples should **not** be exported. + +Never hand-edit the exported `.py` module files — they're build artifacts regenerated from the notebook by `nbdev_export`. All edits go through the source notebook in `nbs/`. + +## Notebook Structure + +Every notebook must follow this structure: + +**Cell 1 — Markdown frontmatter:** +```markdown +# Module Title + +> A one-line description of what this module does +``` +The H1 becomes the page title in docs. The blockquote becomes the subtitle. + +**Cell 2 — Default export:** +```python +#| default_exp module_name +``` + +**Body cells** — alternating between exported code, demonstrations, and markdown explanations (see Cell Structure below). + +**Last cell:** +```python +#| hide +import nbdev; nbdev.nbdev_export() +``` + +Before declaring any notebook task complete, restart the kernel and run all cells top-to-bottom to verify it is fully reproducible. + +## Cell Structure + +Keep cells short. Each exported function gets its own cell, immediately followed by a demonstration. Do not write long functions with comments interspersed — split them into small separate cells with explanations and working examples after each. + +The pattern per concept: + +1. *(Optional)* A markdown cell explaining what comes next +2. A `#| export` code cell with the function +3. One or more plain code cells demonstrating usage +4. Assertions that double as tests + +Example: +```python +#| export +def slugify(text: str) -> str: + "Convert text to a URL-safe slug" + return re.sub(r"[^a-z0-9]+", "-", text.lower()).strip("-") +``` +```python +slug = slugify("Hello, World!") +assert slug == "hello-world" +slug +``` + +## Docstrings and Parameter Documentation + +Keep docstrings short — a single-line summary is sufficient for most functions. Elaborate in separate markdown or code cells below, where you can use real examples. + +Use **docments** (inline parameter comments) instead of verbose docstring parameter sections: + +```python +#| export +def greet( + name: str, # Person to greet + greeting: str="Hi", # Greeting word to use +) -> str: # The composed greeting + "Compose a greeting for name" + return f"{greeting}, {name}!" +``` + +This renders as a clean parameter table in the docs automatically — no need to repeat type information in the docstring body. + +Use backticks around symbol names in docstrings and markdown — nbdev automatically converts these to hyperlinks to the relevant reference page. + +## Code Style + +- **Prefer composition**: write small functions that do one thing well +- Each exported function should be focused enough to fit naturally in a single notebook cell — one cell, one idea +- Use type hints on all exported functions +- Avoid classes unless state is genuinely needed — prefer functions that take and return data +- If you do write a class, use `fastcore`'s `@patch` decorator to define each method in its own cell, immediately followed by a demonstration. This avoids long class definitions and keeps examples close to the code + +When a class is needed, document its methods with `show_doc`: +```python +from nbdev.showdoc import show_doc +show_doc(MyClass.my_method) +``` + +## Tests + +Every code cell is run as a test by nbdev unless explicitly marked otherwise — any exception fails the test. + +- Turn demonstrations into tests by adding `assert` statements +- Use `fastcore.test` helpers for better error messages: + ```python + from fastcore.test import test_eq, test_fail + test_eq(slugify("Hello World"), "hello-world") + ``` +- Document expected error cases with `test_fail`: + ```python + test_fail(lambda: slugify(""), contains="empty") + ``` +- Each test/demo cell should import what it needs directly — don't rely on a name imported in a later cell just because it happened to be in scope during a prior run + +## Execution + +- Always execute cells after writing them to verify they work +- If a cell errors, read the full traceback before attempting a fix — do not guess +- When installing packages, use `%pip install` inside the notebook (not `!pip install`) so they install into the running kernel +- Use autoreload at the top of notebooks that import from other modules in the project: + ```python + %load_ext autoreload + %autoreload 2 + ``` + +## Documentation + +- Use H2 (`##`) markdown cells to group related symbols within a notebook +- Use H4 (`####`) markdown cells to split long explanations within a symbol's section (notes, examples, edge cases, etc.) +- Add rich representations to classes via `_repr_markdown_` where it aids understanding +- Include real code examples, plots, and diagrams — notebooks support rich output, use it + +## Outputs + +- Never print secrets, tokens, passwords, or API keys into cell output — notebook outputs get committed to git and published in docs, unlike transient script output +- Prefer summaries over dumping large data structures (`.head()`, `len()`, `[:5]`, etc.) +- Large outputs consume context window — keep them concise diff --git a/modules/claude-code/skills/remove-skills/SKILL.md b/modules/claude-code/skills/remove-skills/SKILL.md new file mode 100644 index 0000000..e23ea0f --- /dev/null +++ b/modules/claude-code/skills/remove-skills/SKILL.md @@ -0,0 +1,37 @@ +--- +name: remove-skills +description: Remove one or more previously added library skills from the current project. +disable-model-invocation: true +--- + +Removes a skill that [`setup-skills`](../setup-skills/SKILL.md) previously +copied into the current project, deleting both its files and its entry in +`.claude/skills-lock.yaml` (see [LOCKFILE.md](../setup-skills/LOCKFILE.md) +for its schema). + +## Steps + +1. Read `.claude/skills-lock.yaml`. If it doesn't exist or is empty, tell + the user there's nothing installed to remove and stop. + +2. Determine which skill(s) to remove: + - If the user's invocation already named a specific skill, use that — + if it isn't in the lockfile, say so and stop. + - Otherwise, list every skill currently in the lockfile and ask the + user to pick one (or more). + +3. For each skill to remove, compute its current hash + (`~/.claude/skills/setup-skills/hash-dir.sh .claude/skills/`) + and compare it to the hash stored in the lockfile: + - If it matches (never modified since it was installed), delete + `.claude/skills//` and remove its lockfile entry immediately — + no confirmation needed, since nothing of the user's is being lost. + - If it differs (locally customized), tell the user it has local + changes that will be permanently lost and ask for confirmation + before deleting. If they decline, leave that skill installed and + move on to the next. + +4. Finish with a summary of what was removed and what was left in place. + +Done when every skill to remove has been either deleted (with its lockfile +entry removed) or explicitly left in place with a stated reason. diff --git a/modules/claude-code/skills/review-uncommitted/SKILL.md b/modules/claude-code/skills/review-uncommitted/SKILL.md new file mode 100644 index 0000000..f9d2841 --- /dev/null +++ b/modules/claude-code/skills/review-uncommitted/SKILL.md @@ -0,0 +1,147 @@ +--- +name: review-uncommitted +description: Review the working tree's uncommitted changes along three axes — change risk, repo standards, and spec fidelity — using parallel sub-agents. +--- + +Three-axis review of the diff between `HEAD` and the working tree: + +- **Risk** — how much attention does this change warrant, from low to high? +- **Standards** — does the code conform to this repo's documented coding standards? +- **Spec** — does the code faithfully implement the originating PRD or task file? + +All three axes run as **parallel sub-agents** so they don't pollute each other's context, then this skill aggregates their findings. + +## Process + +### 1. Capture the diff + +The diff command is `git diff HEAD` — everything uncommitted, staged or not. +New files must already be tracked (`git add`ed) to show up; this skill doesn't scan for untracked files, so that's the caller's responsibility. + +Confirm the diff is non-empty before going further. +An empty diff should fail here — not inside three parallel sub-agents. + +### 2. Identify the spec source + +Look for the originating spec, in this order: + +1. A path the user passed as an argument. +2. A spec file matching the branch name or feature — `.claude/spec/.md`. +3. If nothing is found, ask the user where the spec is. + If they say there isn't one, the **Spec** sub-agent will skip and report "no spec available". + +### 3. Identify the standards sources + +Anything in the repo that documents how code should be written, such as `CODING_STANDARDS.md` or `CONTRIBUTING.md`. + +On top of whatever the repo documents, the Standards axis always carries the **smell baseline** below — a fixed set of Fowler code smells (_Refactoring_, ch.3) that applies even when a repo documents nothing. +Two rules bind it: + +- **The repo overrides.** + A documented repo standard always wins; where it endorses something the baseline would flag, suppress the smell. +- **Always a judgement call.** + Each smell is a labelled heuristic ("possible Feature Envy"), never a hard violation — and, like any standard here, skip anything tooling already enforces. + +Each smell reads *what it is* → *how to fix*; match it against the diff: + +- **Mysterious Name** — a function, variable, or type whose name doesn't reveal what it does or holds. + → rename it; if no honest name comes, the design's murky. +- **Duplicated Code** — the same logic shape appears in more than one hunk or file in the change. + → extract the shared shape, call it from both. +- **Feature Envy** — a method that reaches into another object's data more than its own. + → move the method onto the data it envies. +- **Data Clumps** — the same few fields or params keep travelling together (a type wanting to be born). + → bundle them into one type, pass that. +- **Primitive Obsession** — a primitive or string standing in for a domain concept that deserves its own type. + → give the concept its own small type. +- **Repeated Switches** — the same `switch`/`if`-cascade on the same type recurs across the change. + → replace with polymorphism, or one map both sites share. +- **Shotgun Surgery** — one logical change forces scattered edits across many files in the diff. + → gather what changes together into one module. +- **Divergent Change** — one file or module is edited for several unrelated reasons. + → split so each module changes for one reason. +- **Speculative Generality** — abstraction, parameters, or hooks added for needs the spec doesn't have. + → delete it; inline back until a real need shows. +- **Message Chains** — long `a.b().c().d()` navigation the caller shouldn't depend on. + → hide the walk behind one method on the first object. +- **Middle Man** — a class or function that mostly just delegates onward. + → cut it, call the real target direct. +- **Refused Bequest** — a subclass or implementer that ignores or overrides most of what it inherits. + → drop the inheritance, use composition. + +### 4. Risk rubric + +The Risk axis judges the diff alone — no repo-doc lookup, no input from the Standards or Spec sub-agents. +It always runs; it only needs the diff from step 1. + +Rate each of these six factors **Low / Medium / High**, then take the single highest-rated factor as the overall rating (worst-factor-wins): + +- **Blast radius** — isolated change vs. ripples across many files, modules, or callers. +- **Reversibility** — trivial rollback vs. hard to undo (migrations, deletions, published API/schema changes). +- **Test coverage** — covered by tests in/around the diff vs. untested. +- **Sensitive domain** — touches auth, security, payments, permissions, concurrency, or data migrations. +- **Size & complexity** — large diff or tangled control flow vs. small/simple. +- **Runtime criticality** — hot path/production-critical vs. internal or dev-only tooling. + +### 5. Spawn all three sub-agents in parallel + +Send a single message with three `Agent` tool calls. +Use the `general-purpose` subagent for all three. + +**Risk sub-agent prompt** — include: + +- The full diff (output of `git diff HEAD`). +- The six risk factors from step 4, pasted in full. +- The brief: "Rate each of the six factors Low/Medium/High with a one-clause reason, then give the overall rating as the highest of the six. + Report the overall rating first, then the six factor lines. + Under 200 words." + +**Standards sub-agent prompt** — include: + +- The full diff (output of `git diff HEAD`). +- The list of standards-source files you found in step 3, **plus the smell baseline from step 3** pasted in full — the sub-agent has no other access to it. +- The brief: "Report — per file/hunk where relevant — (a) every place the diff violates a documented standard: cite the standard (file + the rule); and (b) any baseline smell you spot: name it and quote the hunk. + Distinguish hard violations from judgement calls — documented-standard breaches can be hard, but baseline smells are always judgement calls, and a documented repo standard overrides the baseline. + Skip anything tooling enforces. + Under 400 words." + +**Spec sub-agent prompt** — include: + +- The full diff (output of `git diff HEAD`). +- The path or fetched contents of the spec. +- The brief: "Report: (a) requirements the spec asked for that are missing or partial; (b) behaviour in the diff that wasn't asked for (scope creep); (c) requirements that look implemented but where the implementation looks wrong. + Quote the spec line for each finding. + Under 400 words." + +If the spec is missing, skip the Spec sub-agent and note this in the final report. + +### 6. Aggregate + +Present the Risk report first, under a `## Risk` heading, with the overall rating bolded on its own line followed by the six factor lines: + +``` +## Risk +**Overall: HIGH** +- Blast radius: ... +- Reversibility: ... +- Test coverage: ... +- Sensitive domain: ... +- Size & complexity: ... +- Runtime criticality: ... +``` + +Then present the Standards and Spec reports under `## Standards` and `## Spec` headings, verbatim or lightly cleaned. +Do **not** merge or rerank findings — the axes are deliberately separate (see _Why Standards and Spec stay separate_). + +End with a one-line summary: total findings per axis (Standards/Spec only), and the worst issue _within each axis_ (if any). +Don't pick a single winner across axes — that's the reranking the separation exists to prevent. +The risk rating isn't repeated here; it already leads the report. + +## Why Standards and Spec stay separate + +A change can pass one axis and fail the other: + +- Code that follows every standard but implements the wrong thing → **Standards pass, Spec fail.** +- Code that does exactly what the PRD or task asked but breaks the project's conventions → **Spec pass, Standards fail.** + +Reporting them separately stops one axis from masking the other. diff --git a/modules/claude-code/skills/setup-skills/LOCKFILE.md b/modules/claude-code/skills/setup-skills/LOCKFILE.md new file mode 100644 index 0000000..3ffa79d --- /dev/null +++ b/modules/claude-code/skills/setup-skills/LOCKFILE.md @@ -0,0 +1,53 @@ +# Skills Lockfile + +`.claude/skills-lock.yaml`, at the root of a project, tracks which library +skills (from `~/.claude/skills/library/`) have been copied into that +project's `.claude/skills/`, so [`setup-skills`](SKILL.md), +[`update-skills`](../update-skills/SKILL.md), and +[`remove-skills`](../remove-skills/SKILL.md) all agree on what's installed +without re-deriving it from the filesystem. + +## Schema + +A YAML list of entries, one per installed skill: + +```yaml +- name: nbdev + hash: 3f2a9b8c1d4e5f6a7b8c9d0e1f2a3b4c5d6e7f8a9b0c1d2e3f4a5b6c7d8e9f0a +- name: terraform-conventions + hash: 9c1d4e5f6a7b8c9d0e1f2a3b4c5d6e7f8a9b0c1d2e3f4a5b6c7d8e9f0a3f2a9b +``` + +- `name` — matches both the skill's directory name in the library + (`skills/library/`) and its copied directory name in the project + (`.claude/skills/`). +- `hash` — the output of `hash-dir.sh` run against that one skill's + directory contents, recorded at the moment it was last copied or + confirmed up to date. Never a hash of anything else — not the whole + project, not the whole library, just that one skill's own directory + tree. + +## What a mismatch means + +To classify a skill's state, compare three values: the lockfile's stored +`hash`, `hash-dir.sh` on the project's current copy +(`.claude/skills/`), and `hash-dir.sh` on the library's current +source (`~/.claude/skills/library/`). + +| stored vs. project copy | stored vs. library source | meaning | +|--------------------------|----------------------------|--------------------------------------| +| match | match | nothing to do | +| match | differs | library moved on — safe to update | +| differs | match | project customized on purpose — leave it | +| differs | differs | conflict — report, don't touch | + +## Writing to the lockfile + +- Adding a skill: append a new `{name, hash}` entry. +- Applying a safe update: overwrite that entry's `hash` in place with the + library's current hash. +- Removing a skill: delete its entry entirely. + +Never reorder or restructure existing entries beyond what an add, update, +or remove requires — this file is meant to diff cleanly in a project's +git history. diff --git a/modules/claude-code/skills/setup-skills/SKILL.md b/modules/claude-code/skills/setup-skills/SKILL.md new file mode 100644 index 0000000..74d924e --- /dev/null +++ b/modules/claude-code/skills/setup-skills/SKILL.md @@ -0,0 +1,46 @@ +--- +name: setup-skills +description: Add relevant skills from the shared skills library to the current project. +disable-model-invocation: true +--- + +Adds opt-in, project-specific skills from `~/.claude/skills/library/` into +the current project's `.claude/skills/`, tracked in +`.claude/skills-lock.yaml` (see [LOCKFILE.md](LOCKFILE.md) for its schema). +Only ever adds — checking already-installed skills for updates is +[`update-skills`](../update-skills/SKILL.md)'s job, not this one's. + +## Steps + +1. Read `.claude/skills-lock.yaml` in the current project, if it exists. + Note every skill name already listed — these are already installed and + must not be re-proposed. + +2. List every skill under `~/.claude/skills/library/*/SKILL.md` and read + each one's `name` and `description`. + +3. Inspect the current project (file tree, manifests like + `pyproject.toml`/`package.json`, file extensions present, etc.) and + judge which library skills — excluding ones already installed — seem + relevant, the same way you'd reason about any unfamiliar codebase. + Propose that shortlist to the user with your reasoning, one line per + skill. If the user asks to see the full catalog instead, list every + library skill (minus already-installed ones) with its description. + +4. Let the user confirm, adjust, or pick freely from the full list. + +5. For each confirmed skill: + - If `.claude/skills//` already exists in the project and is + *not* in the lockfile, skip it and tell the user why (a same-named + skill already lives there and isn't tracked — remove or rename it + first if they want the library version). + - Otherwise, copy `~/.claude/skills/library//` to + `.claude/skills//` in the project, run + `~/.claude/skills/setup-skills/hash-dir.sh .claude/skills/`, + and append `{name, hash: }` to `.claude/skills-lock.yaml` + (create the file, an empty YAML list, if it doesn't exist yet). + +6. Report what was added and what was skipped, and why. + +Done when every confirmed skill is either copied and recorded in the +lockfile, or explicitly skipped with a stated reason. diff --git a/modules/claude-code/skills/setup-skills/hash-dir.sh b/modules/claude-code/skills/setup-skills/hash-dir.sh new file mode 100755 index 0000000..267fee7 --- /dev/null +++ b/modules/claude-code/skills/setup-skills/hash-dir.sh @@ -0,0 +1,23 @@ +#!/usr/bin/env bash +# Deterministic recursive hash of a directory's file contents. +# +# Hashes relative paths, not absolute ones, so two directories with +# identical contents hash identically regardless of where they live on +# disk (needed to compare a project's copied skill against the library +# source it was copied from). +# +# Usage: hash-dir.sh +set -euo pipefail + +if [ $# -ne 1 ]; then + echo "Usage: hash-dir.sh " >&2 + exit 1 +fi + +dir="$1" +if [ ! -d "$dir" ]; then + echo "Not a directory: $dir" >&2 + exit 1 +fi + +(cd "$dir" && find . -type f -print0 | sort -z | xargs -0 -r sha256sum) | sha256sum | awk '{print $1}' diff --git a/modules/claude-code/skills/test-driven-development/SKILL.md b/modules/claude-code/skills/test-driven-development/SKILL.md new file mode 100644 index 0000000..8c7db92 --- /dev/null +++ b/modules/claude-code/skills/test-driven-development/SKILL.md @@ -0,0 +1,153 @@ +--- +name: test-driven-development +description: Test-driven development. Use when the user wants to build features or fix bugs test-first, mentions "red-green-refactor", or wants integration tests. +--- + +# Test-Driven Development + +## Philosophy + +**Core principle**: Tests should verify behavior through public interfaces, not implementation details. Code can change entirely; tests shouldn't. + +**Good tests** are integration-style: they exercise real code paths through public APIs. They describe _what_ the system does, not _how_ it does it. A good test reads like a specification - "user can checkout with valid cart" tells you exactly what capability exists. These tests survive refactors because they don't care about internal structure. + +**Bad tests** are coupled to implementation. They mock internal collaborators, test private methods, or verify through external means (like querying a database directly instead of using the interface). The warning sign: your test breaks when you refactor, but behavior hasn't changed. If you rename an internal function and tests fail, those tests were testing implementation, not behavior. + +**Tautological tests** restate the implementation inside the assertion, so they pass by construction and give zero confidence. When the expected value is computed the way the code computes it — `expect(add(a, b)).toBe(a + b)`, snapshotting a figure you derived by hand the same way the code does, asserting a constant equals itself — the test can never disagree with the code: break the code wrong and the assertion breaks wrong with it. The expected value must come from an independent source of truth — a known-good literal, a worked example, the spec. + +See [tests.md](tests.md) for examples and [mocking.md](mocking.md) for mocking guidelines. + +## Anti-Pattern: Horizontal Slices + +**DO NOT write all tests first, then all implementation.** This is "horizontal slicing" - treating RED as "write all tests" and GREEN as "write all code." + +This produces **crap tests**: + +- Tests written in bulk test _imagined_ behavior, not _actual_ behavior +- You end up testing the _shape_ of things (data structures, function signatures) rather than user-facing behavior +- Tests become insensitive to real changes - they pass when behavior breaks, fail when behavior is fine +- You outrun your headlights, committing to test structure before understanding the implementation + +**Correct approach**: Vertical slices via tracer bullets. One test → one implementation → repeat. Each test responds to what you learned from the previous cycle. + +The test-writer sub-agent (below) is handed **one behavior at a time** and never sees the behavior backlog, so it can't bulk-write the suite. + +``` +WRONG (horizontal): + RED: test1, test2, test3, test4, test5 + GREEN: impl1, impl2, impl3, impl4, impl5 + +RIGHT (vertical): + RED→GREEN: test1→impl1 + RED→GREEN: test2→impl2 + RED→GREEN: test3→impl3 + ... +``` + +## Roles + +Every test is written by a **test-writer sub-agent**. The main agent writes every line of implementation, and never writes or edits a test. + +The sub-agent must not read the implementation source of the module under test — that is what keeps its tests from asserting _how_ instead of _what_. It works from the public interface alone. + +Use one `general-purpose` sub-agent for the whole task: spawn it at the first RED, then continue it with `SendMessage` for each subsequent RED, so it keeps the test file and conventions it established. Cold-spawn a replacement only if its ID is lost. + +### Test-writer sub-agent prompt — include: + +- **One behavior**, quoted verbatim from the acceptance criterion or the agreed behavior list. Never the task file, never the rest of the list. +- The **public interface** under test — signatures only. +- The existing test file(s) for the module, and the project's test conventions (fixtures, helpers, runner invocation). +- [tests.md](tests.md) and [mocking.md](mocking.md). +- The **independent source of truth for the expected value** — the spec excerpt, worked example, or known-good literal. Without it the sub-agent recomputes the expected value the way the code would, and the test is tautological. +- `.claude/CONTEXT.md` (if it exists) and any ADRs in the area, so test names and interface vocabulary match the project's domain language. +- The test-side checklist from [Checklist Per Cycle](#checklist-per-cycle), pasted in full — the sub-agent has no other access to it. +- The brief: "Write ONE test for this behavior. Do not read the implementation source of the module under test. Write it to the test file, run it, and confirm it fails with a genuine assertion failure — not an import, syntax, or collection error, which prove nothing. Report the test's name and the exact failure message you saw." + +## Workflow + +### 1. Planning + +When exploring the codebase, read `.claude/CONTEXT.md` (if it exists) so that test names and interface vocabulary match the project's domain language, and respect ADRs in the area you're touching. + +Identify opportunities for deep modules (small interface, deep implementation) — run the `/codebase-design` skill for the vocabulary and the testability checks. Do this regardless of what triggered this workflow. + +**If a task file is already in context** (e.g. passed to `/implement`, which called this skill), its acceptance criteria are the behavior list to test — the interface and priorities were already agreed during `/to-spec` and `/to-tasks`. Don't re-confirm them with the user; go straight to the tracer bullet. + +**Otherwise**, before writing any code: + +- [ ] Confirm with user what interface changes are needed +- [ ] Confirm with user which behaviors to test (prioritize) +- [ ] List the behaviors to test (not implementation steps) +- [ ] Get user approval on the plan + +Ask: "What should the public interface look like? Which behaviors are most important to test?" + +**You can't test everything.** Confirm with the user exactly which behaviors matter most. Focus testing effort on critical paths and complex logic, not every possible edge case. + +Planning stays with the main agent on both paths — exploration, interface, and the order behaviors are tested in. The sub-agent receives behaviors one at a time; it never chooses what to test next. + +### 2. Tracer Bullet + +ONE test that confirms ONE thing about the system: + +``` +RED: Spawn the test-writer sub-agent with the first behavior → it writes the test, runs it, reports a genuine failure +GREEN: Main agent writes minimal code to pass → test passes +``` + +This is your tracer bullet - proves the path works end-to-end. + +### 3. Incremental Loop + +For each remaining behavior: + +``` +RED: SendMessage the same sub-agent the next behavior → it writes the test, runs it, reports a genuine failure +GREEN: Main agent writes minimal code to pass → passes +``` + +Rules: + +- One test at a time +- Only enough code to pass current test +- Don't anticipate future tests +- Keep tests focused on observable behavior + +### When a test looks wrong + +The main agent never edits a sub-agent-authored test — not to fix an import, not to "simplify" an assertion, not to reach GREEN. + +- **Mechanical defect** — bad import path, a fixture or helper that doesn't exist, doesn't parse. Send the error output back to the sub-agent and let it fix its own test. +- **Semantic disagreement** — you believe the expected value or the asserted behavior is wrong. Stop and ask the user. Do not resolve it yourself; this disagreement is the signal the sub-agent exists to surface, and half the time it's the code that's wrong. + +### 4. Refactor + +After all tests pass, look for [refactor candidates](refactoring.md): + +- [ ] Extract duplication +- [ ] Deepen modules (move complexity behind simple interfaces) +- [ ] Apply SOLID principles where natural +- [ ] Consider what new code reveals about existing code +- [ ] Run tests after each refactor step + +**Never refactor while RED.** Get to GREEN first. + +A test that breaks during refactor means the refactor broke behavior — fix the code. The one exception is a public interface change you made deliberately (a module deepened, a signature moved, as agreed in the plan): send the interface change to the sub-agent and let it update its own tests. There is no case where the main agent edits the test itself. + +## Checklist Per Cycle + +Test-writer sub-agent, per test — paste into its prompt: + +``` +[ ] Test describes behavior, not implementation +[ ] Test uses public interface only +[ ] Test would survive internal refactor +[ ] Expected values are independent literals, not recomputed from the code +``` + +Main agent, per GREEN: + +``` +[ ] Code is minimal for this test +[ ] No speculative features added +``` diff --git a/modules/claude-code/skills/test-driven-development/mocking.md b/modules/claude-code/skills/test-driven-development/mocking.md new file mode 100644 index 0000000..71cbfee --- /dev/null +++ b/modules/claude-code/skills/test-driven-development/mocking.md @@ -0,0 +1,59 @@ +# When to Mock + +Mock at **system boundaries** only: + +- External APIs (payment, email, etc.) +- Databases (sometimes - prefer test DB) +- Time/randomness +- File system (sometimes) + +Don't mock: + +- Your own classes/modules +- Internal collaborators +- Anything you control + +## Designing for Mockability + +At system boundaries, design interfaces that are easy to mock: + +**1. Use dependency injection** + +Pass external dependencies in rather than creating them internally: + +```typescript +// Easy to mock +function processPayment(order, paymentClient) { + return paymentClient.charge(order.total); +} + +// Hard to mock +function processPayment(order) { + const client = new StripeClient(process.env.STRIPE_KEY); + return client.charge(order.total); +} +``` + +**2. Prefer SDK-style interfaces over generic fetchers** + +Create specific functions for each external operation instead of one generic function with conditional logic: + +```typescript +// GOOD: Each function is independently mockable +const api = { + getUser: (id) => fetch(`/users/${id}`), + getOrders: (userId) => fetch(`/users/${userId}/orders`), + createOrder: (data) => fetch('/orders', { method: 'POST', body: data }), +}; + +// BAD: Mocking requires conditional logic inside the mock +const api = { + fetch: (endpoint, options) => fetch(endpoint, options), +}; +``` + +The SDK approach means: +- Each mock returns one specific shape +- No conditional logic in test setup +- Easier to see which endpoints a test exercises +- Type safety per endpoint diff --git a/modules/claude-code/skills/test-driven-development/refactoring.md b/modules/claude-code/skills/test-driven-development/refactoring.md new file mode 100644 index 0000000..2b3cd01 --- /dev/null +++ b/modules/claude-code/skills/test-driven-development/refactoring.md @@ -0,0 +1,10 @@ +# Refactor Candidates + +After TDD cycle, look for: + +- **Duplication** → Extract function/class +- **Long methods** → Break into private helpers (keep tests on public interface) +- **Shallow modules** → Combine or deepen — see DEEPENING.md in `/codebase-design` for dependency categories and seam discipline +- **Feature envy** → Move logic to where data lives +- **Primitive obsession** → Introduce value objects +- **Existing code** the new code reveals as problematic diff --git a/modules/claude-code/skills/test-driven-development/tests.md b/modules/claude-code/skills/test-driven-development/tests.md new file mode 100644 index 0000000..7ab8647 --- /dev/null +++ b/modules/claude-code/skills/test-driven-development/tests.md @@ -0,0 +1,77 @@ +# Good and Bad Tests + +## Good Tests + +**Integration-style**: Test through real interfaces, not mocks of internal parts. + +```typescript +// GOOD: Tests observable behavior +test("user can checkout with valid cart", async () => { + const cart = createCart(); + cart.add(product); + const result = await checkout(cart, paymentMethod); + expect(result.status).toBe("confirmed"); +}); +``` + +Characteristics: + +- Tests behavior users/callers care about +- Uses public API only +- Survives internal refactors +- Describes WHAT, not HOW +- One logical assertion per test + +## Bad Tests + +**Implementation-detail tests**: Coupled to internal structure. + +```typescript +// BAD: Tests implementation details +test("checkout calls paymentService.process", async () => { + const mockPayment = jest.mock(paymentService); + await checkout(cart, payment); + expect(mockPayment.process).toHaveBeenCalledWith(cart.total); +}); +``` + +Red flags: + +- Mocking internal collaborators +- Testing private methods +- Asserting on call counts/order +- Test breaks when refactoring without behavior change +- Test name describes HOW not WHAT +- Verifying through external means instead of interface + +```typescript +// BAD: Bypasses interface to verify +test("createUser saves to database", async () => { + await createUser({ name: "Alice" }); + const row = await db.query("SELECT * FROM users WHERE name = ?", ["Alice"]); + expect(row).toBeDefined(); +}); + +// GOOD: Verifies through interface +test("createUser makes user retrievable", async () => { + const user = await createUser({ name: "Alice" }); + const retrieved = await getUser(user.id); + expect(retrieved.name).toBe("Alice"); +}); +``` + +**Tautological tests**: Expected value restates the implementation, so the test passes by construction. + +```typescript +// BAD: Expected value is recomputed the way the code computes it +test("calculateTotal sums line items", () => { + const items = [{ price: 10 }, { price: 5 }]; + const expected = items.reduce((sum, i) => sum + i.price, 0); + expect(calculateTotal(items)).toBe(expected); +}); + +// GOOD: Expected value is an independent, known literal +test("calculateTotal sums line items", () => { + expect(calculateTotal([{ price: 10 }, { price: 5 }])).toBe(15); +}); +``` diff --git a/modules/claude-code/skills/to-spec/SKILL.md b/modules/claude-code/skills/to-spec/SKILL.md new file mode 100644 index 0000000..489c610 --- /dev/null +++ b/modules/claude-code/skills/to-spec/SKILL.md @@ -0,0 +1,21 @@ +--- +name: to-spec +description: Turn the current conversation into a spec and write it to .claude/spec/ — no interview, just synthesis of what you've already discussed. +disable-model-invocation: true +--- + +This skill takes the current conversation context and codebase understanding and produces a spec. Do NOT interview the user — just synthesize what you already know. + +If the conversation doesn't actually contain a feature or problem to synthesize a spec from, say so and ask what it's for instead of fabricating one. + +## Process + +1. Explore the repo until you can name the existing modules, flows, and seams the feature will touch, if you haven't already. Use the project's domain glossary vocabulary throughout the spec, and respect any ADRs in the area you're touching. + +2. Sketch out the seams at which you're going to test the feature. Existing seams should be preferred to new ones. Use the highest seam possible. If new seams are needed, propose them at the highest point you can. The fewer seams across the codebase, the better - the ideal number is one. + +Check with the user that these seams match their expectations. + +3. Derive a short kebab-case feature-slug from the feature's name (e.g. `checkout-flow`). Tell the user the path you're about to write to (`.claude/spec/.md`). If a file already exists there, summarize what would change and confirm with the user before overwriting it — never overwrite silently. + +4. Write the spec using the format in [SPEC-FORMAT.md](./SPEC-FORMAT.md) to `.claude/spec/.md`, creating the `.claude/spec/` directory if it doesn't exist yet. diff --git a/modules/claude-code/skills/to-spec/SPEC-FORMAT.md b/modules/claude-code/skills/to-spec/SPEC-FORMAT.md new file mode 100644 index 0000000..15e82f8 --- /dev/null +++ b/modules/claude-code/skills/to-spec/SPEC-FORMAT.md @@ -0,0 +1,55 @@ +# Spec Format + +## Template + +```md +## Problem Statement + +The problem that the user is facing, from the user's perspective. + +## Solution + +The solution to the problem, from the user's perspective. + +## User Stories + +An extensive, numbered list of user stories, covering all aspects of the feature. Each user story should be in the format of: + +1. As an , I want a , so that + + +1. As a mobile bank customer, I want to see balance on my accounts, so that I can make better informed decisions about my spending + + +## Implementation Decisions + +A list of implementation decisions that were made. This can include: + +- The modules that will be built/modified +- The interfaces of those modules that will be modified +- Technical clarifications from the developer +- Architectural decisions +- Schema changes +- API contracts +- Specific interactions + +## Testing Decisions + +A list of testing decisions that were made. Include: + +- A description of what makes a good test (only test external behavior, not implementation details) +- Which modules will be tested +- Prior art for the tests (i.e. similar types of tests in the codebase) + +## Out of Scope + +A description of the things that are out of scope for this spec. + +## Further Notes + +Any further notes about the feature. +``` + +## Rules + +- **Don't include specific file paths or code snippets.** They may end up being outdated very quickly. Exception: if a prototype produced a snippet that encodes a decision more precisely than prose can (state machine, reducer, schema, type shape), inline it within the relevant decision in Implementation Decisions and note briefly that it came from a prototype. Trim to the decision-rich parts — not a working demo, just the important bits. diff --git a/modules/claude-code/skills/to-tasks/SKILL.md b/modules/claude-code/skills/to-tasks/SKILL.md new file mode 100644 index 0000000..112d382 --- /dev/null +++ b/modules/claude-code/skills/to-tasks/SKILL.md @@ -0,0 +1,57 @@ +--- +name: to-tasks +description: Break a plan or spec into independently-grabbable task files under .claude/tasks/ using tracer-bullet vertical slices. +disable-model-invocation: true +--- + +# To Tasks + +## Process + +### 1. Gather context + +Work from whatever is already in the conversation context. If the user passes a spec path or other reference as an argument, read it directly. + +Determine the feature-slug this breakdown belongs to, if any: if a spec file is in context or was passed as an argument, derive it from the filename (`.claude/spec/.md` → ``) for each task's `spec` field — see [TASK-FORMAT.md](./TASK-FORMAT.md) for the field's rules. If no spec file exists, proceed without one. + +### 2. Explore the codebase (optional) + +If you have not already explored the codebase, do so to understand the current state of the code. Task titles and descriptions should use the project's domain glossary vocabulary, and respect ADRs in the area you're touching. + +Look for opportunities to prefactor the code to make the implementation easier. "Make the change easy, then make the easy change." + +### 3. Draft vertical slices + +Break the plan into **tracer bullet** tasks — vertical slices, not horizontal layers. + + + +- Each slice delivers a narrow but COMPLETE path through every layer the change requires (schema, API, UI, tests), never a horizontal slice of just one +- A completed slice is demoable or verifiable on its own +- Any prefactoring should be done first + + + +### 4. Quiz the user + +Number slices with a single sequence shared across every file already in `.claude/tasks/`: scan for the highest existing `NNNN` (four-digit, zero-padded decimal, `0000`-`9999`) and increment from there. Never restart the sequence per feature and never reuse a number. + +Present the proposed breakdown as a numbered list. For each slice, show: + +- **File**: the `NNNN-slice-slug` it will be written as, per the numbering above +- **Blocked by**: which other slices (if any) must complete first — "None" if it can start immediately +- **User stories covered**: which user stories this addresses (if the source material has them) + +Ask the user: + +- Does the granularity feel right? (too coarse / too fine) +- Are the dependency relationships correct? +- Should any slices be merged or split further? + +Iterate until the user approves the breakdown, including the proposed numbers and slugs. + +### 5. Write the task files + +For each approved slice, write a file to `.claude/tasks/-.md` (create the directory if it doesn't exist) using the numbers and slugs approved in step 4. Use the template in [TASK-FORMAT.md](./TASK-FORMAT.md). + +Do NOT modify the parent spec file (`.claude/spec/.md`) when writing tasks. diff --git a/modules/claude-code/skills/to-tasks/TASK-FORMAT.md b/modules/claude-code/skills/to-tasks/TASK-FORMAT.md new file mode 100644 index 0000000..1ac9871 --- /dev/null +++ b/modules/claude-code/skills/to-tasks/TASK-FORMAT.md @@ -0,0 +1,28 @@ +# Task Format + +## Template + +```md +--- +spec: +blocked-by: +--- + +## What to build + +A concise description of this vertical slice. Describe the end-to-end behavior, not layer-by-layer implementation. + +## Acceptance criteria + +- [ ] Criterion 1 +- [ ] Criterion 2 +- [ ] Criterion 3 +``` + +## Rules + +- **`spec`**: the feature-slug this task was written from. Omit the field entirely if there's no spec. +- **`blocked-by`**: which other task(s) must complete before this one can start. Omit the field entirely if there are none. Each value is the blocking task's full `-` filename stem, not just its slug. A single blocker is a bare string (`blocked-by: 0010-add-schema`); more than one is a YAML list (`blocked-by: [0010-add-schema, 0011-wire-api]`). Once written, keep the field even after the referenced task is completed — it's a permanent record of the dependency, not a "still blocked" flag. +- **Don't include specific file paths or code snippets** in "What to build" — they go stale fast. Exception: if a prototype produced a snippet that encodes a decision more precisely than prose can (state machine, reducer, schema, type shape), inline it here and note briefly that it came from a prototype. Trim to the decision-rich parts — not a working demo, just the important bits. +- A task is done when every criterion in "Acceptance criteria" is resolved: mark `[x]` as satisfied, or `[-]` if deliberately dropped (`/implement` records the reason in the task's Implementation Notes) — track completion here, not anywhere else. +- A slice becomes pickable once every task named in `blocked-by` is done (all of its acceptance criteria resolved) — check the referenced tasks' state, not just whether the field is present. The file's number is an identifier and a rough ordering hint, not a strict gate — sibling slices with no blockers can be worked in parallel. diff --git a/modules/claude-code/skills/update-skills/SKILL.md b/modules/claude-code/skills/update-skills/SKILL.md new file mode 100644 index 0000000..2d11a89 --- /dev/null +++ b/modules/claude-code/skills/update-skills/SKILL.md @@ -0,0 +1,52 @@ +--- +name: update-skills +description: Check the current project's installed library skills for upstream changes and apply the safe ones. +disable-model-invocation: true +--- + +Compares every skill listed in the current project's +`.claude/skills-lock.yaml` (see [LOCKFILE.md](../setup-skills/LOCKFILE.md) +for its schema) against both the project's own copy and the current +library source, and decides what to do about each one. Never installs a +skill that isn't already there — that's +[`setup-skills`](../setup-skills/SKILL.md)'s job. + +## Steps + +1. Read `.claude/skills-lock.yaml`. If it doesn't exist or is empty, tell + the user there's nothing to check and stop. + +2. For each `{name, hash}` entry, compute: + - `project_hash`: `~/.claude/skills/setup-skills/hash-dir.sh .claude/skills/` + - `library_hash`: `~/.claude/skills/setup-skills/hash-dir.sh ~/.claude/skills/library/` + + If either path is missing entirely, report that anomaly for this skill + (don't try to classify it) and move on to the next entry. + +3. Classify each entry against the table in + [LOCKFILE.md](../setup-skills/LOCKFILE.md#what-a-mismatch-means), + using `project_hash` in place of "project copy" and `library_hash` in + place of "library source". The two outcomes that need action below are + **safe update** (stored matches project, differs from library) and + **conflict** (stored differs from both). "Locally customized" needs no + message beyond the summary. + +4. If there are any safe updates, list them by name and ask for one + confirmation to apply all of them — unless the user's invocation + already included an explicit go-ahead argument (e.g. `-y`, `yes`), in + which case apply them without asking. Applying means: delete + `.claude/skills//` entirely and copy + `~/.claude/skills/library//` in its place, so no file the project + copy had but the library no longer has can survive — then recompute its + hash and overwrite that entry's `hash` in `.claude/skills-lock.yaml` in + place. + +5. For every conflict, report it and show a recursive diff between the + project's copy and the library's current version + (`diff -ru .claude/skills/ ~/.claude/skills/library/`). + Do not modify the project's copy or the lockfile entry for a + conflicted skill under any circumstances — surfacing it is the whole + job here. + +6. Finish with a summary: updated, left alone (customized), conflicted, + already current, and any anomalies from step 2. -- 2.47.3 From 7810425849ab34f8525df2ff2d2b89ee5cb601d8 Mon Sep 17 00:00:00 2001 From: alexion Date: Sun, 19 Jul 2026 00:54:12 -0400 Subject: [PATCH 25/32] docs(neogaia): document the one-shot install flow Add docs/install.md, the operator runbook that turns the completed neogaia Host into a running encrypted laptop from the NixOS live ISO: push the flake to its remote, join wifi, clone the repo locally, disko-install against neogaia with the chaotic substituter handed to the install-time daemon, set the bootstrap login password by hand via nixos-enter, and reboot. --- .claude/tasks/0009-install-flow-docs.md | 36 ++++++ docs/install.md | 153 ++++++++++++++++++++++++ 2 files changed, 189 insertions(+) create mode 100644 .claude/tasks/0009-install-flow-docs.md create mode 100644 docs/install.md diff --git a/.claude/tasks/0009-install-flow-docs.md b/.claude/tasks/0009-install-flow-docs.md new file mode 100644 index 0000000..a710747 --- /dev/null +++ b/.claude/tasks/0009-install-flow-docs.md @@ -0,0 +1,36 @@ +--- +spec: laptop-mvi +blocked-by: [0002-neogaia-disk-and-boot, 0003-kernel-and-hardware, 0004-networking-and-base-system, 0005-fish-shell-module, 0006-tmux-module, 0007-nvim-module, 0008-claude-code-module] +--- + +## What to build + +Document the one-shot install procedure that turns the completed `neogaia` `Host` into a running encrypted laptop from the NixOS live ISO — the capstone, written once every functional slice is in place so it describes the actually-complete `Host`. + +The procedure: push the repo to Gitea first; from the live ISO, join wifi, clone the repo locally (avoiding self-signed-TLS/auth problems with flake fetching during install), and run `disko-install` against the `neogaia` `Host` with the chaotic substituter passed to the install-time Nix daemon (or it compiles the CachyOS kernel from source on the USB stick). Then set the bootstrap password by hand via `nixos-enter` — never committed to the public repo — and reboot. + +Note the bootstrap ordering (the flake must exist on Gitea before the install can consume it) and that moving the password to a `hashedPasswordFile` backed by a sops secret is the first post-boot task, out of scope here (per ADR 0001, an age key does not exist until the first install generates the SSH host key). + +## Acceptance criteria + +- [x] The install procedure is documented end to end: push to Gitea → join wifi on the live ISO → clone locally → `disko-install` against `neogaia` → set bootstrap password via `nixos-enter` → reboot. +- [x] The docs state that the install-time Nix daemon must have the chaotic substituter configured, or the kernel compiles from source on the USB stick. +- [x] The docs explain that the local clone avoids self-signed-TLS/auth problems with flake fetching during install. +- [x] The bootstrap password is set by hand and never committed; the docs flag the sops-backed `hashedPasswordFile` migration as the first post-boot follow-up. + +## Implementation Notes + +The runbook lives at `docs/install.md`. + +Every documented command was checked against the actual pinned tooling rather than written from memory: + +- The `disko-install` and `disko` flags (`--flake`, `--disk NAME DEVICE`, `--write-efi-boot-entries`, `--option`, `--mode mount`) were read out of the pinned disko revision's wrapped scripts (the disko rev in `flake.lock`). +- A consequence surfaced there and shaped the doc: `disko-install` traps `EXIT` and **unmounts** the target, so the "set the bootstrap password" step must first remount with `disko --mode mount` before `nixos-enter`. + A naive `nixos-enter --root /mnt` straight after the install would have found nothing mounted. +- The chaotic substituter URL and trusted key are quoted verbatim from `system/default.nix`, and `--disk main /dev/nvme0n1` matches `hosts/neogaia/disk.nix`. + +Two secrets are set by hand at install time, not one: the doc distinguishes the **LUKS passphrase** (prompted by disko at format, typed at every boot) from the **bootstrap login password** (set via `nixos-enter passwd`). +The task named only the login password; the LUKS passphrase is an unavoidable part of the same by-hand flow, so it is documented alongside for a complete runbook. + +Beyond the task's terse list, the doc adds: a minimal-vs-graphical ISO split for joining wifi, and — from review — an SSH-key caveat for the clone plus an HTTPS-with-`sslVerify=false` fallback (which also reinforces the "git can skip verification where the flake fetcher can't" point behind the local-clone requirement). +No criteria were dropped. diff --git a/docs/install.md b/docs/install.md new file mode 100644 index 0000000..2b9e6e2 --- /dev/null +++ b/docs/install.md @@ -0,0 +1,153 @@ +# Installing `neogaia` + +This is the one-shot procedure that turns the `neogaia` `Host` in this flake into a running, encrypted Dell XPS 13 laptop, installed from the NixOS live ISO. + +It is destructive: it formats `/dev/nvme0n1` in full. +Read it end to end before starting, because the laptop is the only machine and the reimage is irreversible. + +The whole install is a single `disko-install` against the `neogaia` `Host`, followed by setting a bootstrap login password by hand. +Everything the installed system needs — the LUKS layout, the CachyOS kernel, the wifi firmware, the user, and the terminal tooling — is already declared in the flake, so this document is only the mechanics of getting that flake onto the disk. + +## Bootstrap ordering + +The install consumes the flake from Gitea, so **the flake must already be on Gitea before you start** — the repo cannot pull a config that hasn't been pushed. +The bootstrap login password is likewise set by hand at the end and is **never committed**, which is what keeps the public repo free of any secret while still yielding a working login on first boot. + +Two secrets are set by hand during this install, both entered interactively and neither stored in the repo: + +1. The **LUKS passphrase** that encrypts the disk, entered when `disko-install` formats it and again at every boot. +2. The **bootstrap login password** for the `alexion` user, set through `nixos-enter` just before the reboot. + +## 0. Push the repo to Gitea + +From your working checkout, make sure `main` is committed and pushed to the Gitea remote: + +```console +$ git push origin main +``` + +The install reads only committed, git-tracked content, so anything uncommitted will not make it onto the laptop. + +## 1. Boot the live ISO and join wifi + +Boot the machine from a NixOS live ISO (the minimal installer is enough). +The installer logs in as the `nixos` user, who has passwordless `sudo`. + +On the minimal ISO, bring up wifi with `wpa_supplicant`: + +```console +$ sudo systemctl start wpa_supplicant +$ wpa_cli +> add_network +0 +> set_network 0 ssid "YOUR_SSID" +> set_network 0 psk "YOUR_WIFI_PASSWORD" +> enable_network 0 +> quit +``` + +On the graphical ISO, which ships NetworkManager, use `nmcli` instead: + +```console +$ nmcli device wifi connect "YOUR_SSID" password "YOUR_WIFI_PASSWORD" +``` + +Confirm you have connectivity (`ping -c1 github.com`) before continuing. + +## 2. Clone the repo locally + +Clone this repo onto the live ISO and work from that local checkout: + +```console +$ git clone ssh://gitea@git.alexion.dev:2022/alexion/dotfiles-nixos.git +$ cd dotfiles-nixos +``` + +Cloning over SSH needs your Gitea SSH key present in the live session, since the ISO starts with none. +If getting the key onto the ISO is inconvenient, clone over HTTPS instead and tell git to skip the self-signed certificate: + +```console +$ git -c http.sslVerify=false clone https://git.alexion.dev/alexion/dotfiles-nixos.git +$ cd dotfiles-nixos +``` + +Do **not** point `disko-install` straight at the Gitea flake URL. +Gitea serves HTTPS with a self-signed certificate and expects authentication, and Nix's flake fetcher has no easy way to skip certificate verification or supply those credentials mid-install. +A plain `git clone` sidesteps that entirely — over SSH there is no TLS, and over HTTPS git takes the `sslVerify=false` above that the flake fetcher won't — and then `disko-install` consumes the flake from a local path, where no fetch of our repo happens during the build. +(The public flake inputs — `nixpkgs`, `chaotic`, `disko` — are still fetched from GitHub over ordinary, valid TLS; only our own repo is the problem the local clone solves.) + +## 3. Run `disko-install` + +Run the install as root from inside the clone: + +```console +$ sudo nix --extra-experimental-features 'nix-command flakes' run \ + github:nix-community/disko/latest#disko-install -- \ + --flake .#neogaia \ + --disk main /dev/nvme0n1 \ + --write-efi-boot-entries \ + --option extra-substituters https://nyx-cache.chaotic.cx/ \ + --option extra-trusted-public-keys nyx-cache.chaotic.cx:dJxTrgMC3V3cFfyIiBQDQorG6k1LsqurH/srpMSq7qk= +``` + +What each part does: + +- `--flake .#neogaia` installs the `neogaia` `Host` from the local clone. +- `--disk main /dev/nvme0n1` maps disko's `main` disk to the NVMe device; it matches the device declared in `hosts/neogaia/disk.nix` and is stated explicitly so there is no doubt about the target. +- `--write-efi-boot-entries` writes the systemd-boot entry into this machine's NVRAM, because the disk stays in the laptop it was installed from. +- The two `--option` lines are the important part: they hand the **chaotic binary cache** to the install-time Nix daemon on the live ISO. + +The chaotic substituter must be passed here explicitly. +The `nix.settings` in the flake configure the substituters of the *installed* system, not the live ISO's daemon that runs this build; the ISO's daemon has no `substituters` beyond `cache.nixos.org`. +Without these two `--option` flags, the build cannot fetch the prebuilt CachyOS kernel and **compiles `linuxPackages_cachyos` (and its toolchain) from source on the USB stick** — a very long detour that the cache avoids. +Because the install runs as root, and root is a trusted Nix user, the daemon honours these client-supplied substituter settings. + +Partway through, disko formats the LUKS container and **prompts for a disk-encryption passphrase**. +This is the passphrase you will type at every boot to unlock the disk; choose it deliberately. + +When it finishes it prints `disko-install succeeded`. +`disko-install` unmounts the target filesystem on exit, so nothing is mounted at this point — step 4 remounts it. + +## 4. Set the bootstrap login password + +The installed system was written with no login password (`nixos-install --no-root-password`, and the flake sets none for `alexion`), so it cannot yet be logged into. +Set a bootstrap password by hand before rebooting. + +First remount the just-installed system with disko, which reopens the LUKS container (prompting for the passphrase from step 3) and mounts the subvolumes under `/mnt`: + +```console +$ sudo nix --extra-experimental-features 'nix-command flakes' run \ + github:nix-community/disko/latest#disko -- \ + --mode mount --flake .#neogaia +``` + +Then enter the installed system and set the password for your user: + +```console +$ sudo nixos-enter --root /mnt +[nixos-enter]# passwd alexion +[nixos-enter]# exit +``` + +This password lives only on the laptop's disk; it is **never committed** anywhere. + +## 5. Reboot + +Unmount and reboot into the installed system: + +```console +$ sudo umount -R /mnt +$ sudo reboot +``` + +Remove the USB stick. +At boot you are prompted for the LUKS passphrase from step 3; after unlocking, log in at the console as `alexion` with the bootstrap password from step 4 and you have a working system with fish, tmux, nvim, and Claude Code. + +## First post-boot task + +Setting the login password by hand is a bootstrap shortcut, not the end state. +The first thing to do on the running laptop is to move that password to a `hashedPasswordFile` backed by a `sops-nix` secret, so it is declared and reproducible like everything else. + +This is deliberately out of scope for the install itself. +Per ADR 0001, each `Host`'s secrets are encrypted to an age key derived from that `Host`'s SSH host key — and that host key does not exist until this first install generates it. +So the sops wiring can only happen *after* the machine is up, which is exactly why it is the first follow-up rather than part of this procedure. -- 2.47.3 From 91d0a7d8e43458772ab2b8cffe05cd2750d1aba3 Mon Sep 17 00:00:00 2001 From: alexion Date: Sun, 19 Jul 2026 07:47:54 -0400 Subject: [PATCH 26/32] style: trim verbose comments to concise, self-contained notes Cut restated "what", domain-glossary framing, cross-file consumption narration, and against-alternative justification from in-file comments; keep only non-obvious "why" and load-bearing pointers. Drop the `generateCompletions` line (a no-op restatement of the upstream default) and its comment. --- flake.nix | 22 +++++++++----------- hosts/neogaia/default.nix | 24 ++++++---------------- hosts/neogaia/disk.nix | 12 +++-------- hosts/neogaia/hardware-configuration.nix | 3 +-- lib/default.nix | 15 ++++++-------- modules/claude-code/claude-code.nix | 19 ++++++----------- modules/example.nix | 7 +++---- modules/fish/fish.nix | 26 +++++++----------------- modules/nvim/nvim.nix | 14 +++++-------- modules/tmux/tmux.nix | 10 ++++----- system/default.nix | 25 ++++++++++------------- 11 files changed, 62 insertions(+), 115 deletions(-) diff --git a/flake.nix b/flake.nix index 0500a27..5b92014 100644 --- a/flake.nix +++ b/flake.nix @@ -1,11 +1,11 @@ { - description = "Alexion's NixOS configuration — one flake for every Host"; + description = "Alexion's NixOS configuration — one flake for every host"; inputs = { - # Base channel: nixos-unstable (rolling, but gated by the NixOS test suite). + # Base channel. nixpkgs.url = "github:nixos/nixpkgs/nixos-unstable"; - # Fresher-than-base packages, reachable per-package as `unstable.`. + # Fresher packages, reachable per-package as `unstable.`. nixpkgs-unstable.url = "github:nixos/nixpkgs/nixpkgs-unstable"; # Latest stable release, reachable per-package as `stable.`. @@ -16,22 +16,20 @@ inputs.nixpkgs.follows = "nixpkgs"; }; - # Neovim configured declaratively in Nix. Must follow our nixpkgs so its - # plugins build against the same package set. + # Follows our nixpkgs so its plugins build against the same package set. nixvim = { url = "github:nix-community/nixvim"; inputs.nixpkgs.follows = "nixpkgs"; }; - # Declarative disk partitioning. Each Host declares its own layout; a Host - # that preserves an existing pool simply declares none. + # Declarative disk partitioning; each host declares its own layout. disko = { url = "github:nix-community/disko"; inputs.nixpkgs.follows = "nixpkgs"; }; - # CachyOS kernel + binary cache. Deliberately NOT following our nixpkgs, so the - # chaotic cache stays usable and the kernel is fetched rather than compiled. + # CachyOS kernel and binary cache. Pins its own nixpkgs so its cache stays + # usable and the kernel is fetched from it. chaotic.url = "github:chaotic-cx/nyx/nyxpkgs-unstable"; }; @@ -42,13 +40,13 @@ my = import ./lib { inherit lib inputs self; }; in { - # The trimmed helper lib: the Auto-loader, the host-builder, the script-from-file helper. + # Helper functions for discovering and building hosts. lib = my; - # Every Host under hosts/ is auto-discovered and built. + # Every host under hosts/ is discovered and built. nixosConfigurations = my.mkHosts (self + "/hosts"); - # `nix flake check` builds each Host's toplevel — the primary test seam. + # `nix flake check` builds each host's toplevel. checks.x86_64-linux = lib.mapAttrs ( _name: host: host.config.system.build.toplevel ) self.nixosConfigurations; diff --git a/hosts/neogaia/default.nix b/hosts/neogaia/default.nix index c8b7778..c5eb008 100644 --- a/hosts/neogaia/default.nix +++ b/hosts/neogaia/default.nix @@ -1,8 +1,6 @@ { pkgs, ... }: # neogaia — Dell XPS 13 9380 laptop. -# -# The disk layout lives in ./disk.nix (disko); the resulting `fileSystems` are -# derived from it, so none are declared by hand here. +# Disk layout is in ./disk.nix; `fileSystems` are derived from it, none declared here. { imports = [ ./hardware-configuration.nix @@ -11,44 +9,34 @@ system.stateVersion = "26.05"; - # systemd-boot on the EFI system partition disko creates. The initrd prompts - # for the LUKS passphrase (disko wires up boot.initrd.luks.devices), so a - # normal boot unlocks the encrypted root. + # systemd-boot on the EFI system partition. boot.loader.systemd-boot.enable = true; boot.loader.efi.canTouchEfiVariables = true; - # neogaia runs the CachyOS kernel, selected per-Host via boot.kernelPackages. boot.kernelPackages = pkgs.linuxPackages_cachyos; - # Intel CPU microcode updates for the XPS 13's Core i7-8565U. hardware.cpu.intel.updateMicrocode = true; - # Redistributable firmware — carries the ath10k blobs the QCA6174 wifi needs. + # Redistributable firmware for the QCA6174 wifi (ath10k blobs). hardware.enableRedistributableFirmware = true; - # Swap is RAM-backed zram rather than an on-disk partition. + # RAM-backed swap; no on-disk swap partition. zramSwap.enable = true; - # NetworkManager drives the wifi so it can be joined from the console. + # So wifi can be joined from the console. networking.networkmanager.enable = true; - # An SSH daemon so the rest of the setup can be driven over the network. + # So setup can be driven over the network. services.openssh.enable = true; # fish as the login shell. modules.fish.enable = true; modules.fish.defaultShell = true; - # tmux as the terminal multiplexer. modules.tmux.enable = true; - - # Neovim, configured declaratively via nixvim. modules.nvim.enable = true; - - # Claude Code, Anthropic's CLI, installed via home-manager. modules.claude-code.enable = true; - # Locale preferences for the base system. time.timeZone = "America/New_York"; i18n.defaultLocale = "en_GB.UTF-8"; console.keyMap = "us"; diff --git a/hosts/neogaia/disk.nix b/hosts/neogaia/disk.nix index 024b062..a7e9dba 100644 --- a/hosts/neogaia/disk.nix +++ b/hosts/neogaia/disk.nix @@ -1,13 +1,7 @@ { ... }: -# neogaia's disk layout, declared with disko and interpreted by the disko module -# the host-builder wires in. This is a per-Host concern: another Host declares a -# different `disko.devices` (or none, preserving an existing pool by importing it). -# -# One NVMe disk, GPT: an EFI system partition for systemd-boot, and a LUKS -# container holding a btrfs filesystem with subvolumes. Swap is zram (RAM-backed), -# so there is deliberately no on-disk swap partition. disko derives the matching -# `fileSystems.*` and `boot.initrd.luks.devices.*` from this, so a normal boot -# prompts for the passphrase in the initrd and unlocks the encrypted root. +# neogaia's disk layout for disko: one NVMe disk, GPT, with an EFI system +# partition and a LUKS container holding btrfs subvolumes. No swap partition; +# swap is zram. disko derives `fileSystems` and `boot.initrd.luks.devices` from this. { disko.devices.disk.main = { type = "disk"; diff --git a/hosts/neogaia/hardware-configuration.nix b/hosts/neogaia/hardware-configuration.nix index 9735cf3..b1e2bbe 100644 --- a/hosts/neogaia/hardware-configuration.nix +++ b/hosts/neogaia/hardware-configuration.nix @@ -1,6 +1,5 @@ { lib, modulesPath, ... }: -# Placeholder hardware profile for the XPS 13: the host platform and the initrd -# modules the machine needs to evaluate and boot. +# Placeholder: regenerate with nixos-generate-config on the target machine. { imports = [ (modulesPath + "/installer/scan/not-detected.nix") ]; diff --git a/lib/default.nix b/lib/default.nix index 525c2be..6318ebe 100644 --- a/lib/default.nix +++ b/lib/default.nix @@ -13,10 +13,8 @@ let mapAttrsToList ; - # --- Auto-loader --------------------------------------------------------- - # Recursively collect every `.nix` file under `dir`, returned as a flat list - # of paths suitable for a module `imports`: a directory recurses, a `.nix` - # file is taken, anything else is skipped. + # Recursively collect every `.nix` file under `dir` as a flat list, for a + # module's `imports`. collectNixFiles = dir: flatten ( @@ -34,10 +32,9 @@ let ) (builtins.readDir dir) ); - # --- Host-builder -------------------------------------------------------- - # Build one Host: every Module is imported unconditionally (inert until its - # `enable` flag is set), alongside home-manager, chaotic, the shared base, - # and the Host's own directory. + # Build one host: every module is imported unconditionally (inert until its + # `enable` flag is set), alongside home-manager, chaotic, the shared base, and + # the host's own directory. mkHost = { hostName, @@ -61,7 +58,7 @@ let ]; }; - # Discover every Host (a subdirectory of `hostsDir`) and build each one. + # Discover every host (a subdirectory of `hostsDir`) and build each one. mkHosts = hostsDir: let diff --git a/modules/claude-code/claude-code.nix b/modules/claude-code/claude-code.nix index c26c089..e47b02a 100644 --- a/modules/claude-code/claude-code.nix +++ b/modules/claude-code/claude-code.nix @@ -3,19 +3,14 @@ lib, ... }: -# Claude Code — Anthropic's CLI — for the primary user, configured declaratively -# through home-manager. home-manager ships the package and manages ~/.claude: -# the global agent instructions (./CLAUDE.md), the skills tree (./skills), the -# attention-bell hook (./hooks), and settings.json (the model and the hook -# wiring). Login credentials are left unmanaged so they survive rebuilds; -# signing in without a browser, as needed over the console or SSH, is covered in -# ./authentication.md. +# Claude Code for the primary user, configured through home-manager, which ships +# the package and manages ~/.claude. Login credentials are left unmanaged so they +# survive rebuilds. let cfg = config.modules.claude-code; user = config.user.name; - # Rings the terminal bell so tmux flags the background pane; wired to both the - # end of a turn and attention notifications below. + # Rings the terminal bell so tmux flags the background pane. bellHook = [ { hooks = [ @@ -37,12 +32,10 @@ in # Global agent instructions, rendered to ~/.claude/CLAUDE.md. context = ./CLAUDE.md; - # One directory per skill, each carrying its SKILL.md, symlinked under - # ~/.claude/skills. + # One directory per skill, symlinked under ~/.claude/skills. skills = ./skills; - # Installed executable at ~/.claude/hooks/attention-bell.sh, where the - # settings hooks reference it. + # Installed at ~/.claude/hooks/attention-bell.sh, referenced by the settings below. hooks."attention-bell.sh" = builtins.readFile ./hooks/attention-bell.sh; settings = { diff --git a/modules/example.nix b/modules/example.nix index a149901..9b45206 100644 --- a/modules/example.nix +++ b/modules/example.nix @@ -1,12 +1,11 @@ { config, lib, ... }: -# The Auto-loader reference example. Every real Module copies this shape: it is -# imported unconditionally but its body stays inert until a Host sets the -# `enable` flag, so each Host reads as a checklist of `enable = true` lines. +# Reference example for the module shape: imported unconditionally, but inert +# until a host sets its `enable` flag. let cfg = config.modules.example; in { - options.modules.example.enable = lib.mkEnableOption "the Auto-loader reference example Module"; + options.modules.example.enable = lib.mkEnableOption "the reference example module"; config = lib.mkIf cfg.enable { environment.etc."skeleton-example".text = "This Module is enabled.\n"; diff --git a/modules/fish/fish.nix b/modules/fish/fish.nix index 19425f9..8432357 100644 --- a/modules/fish/fish.nix +++ b/modules/fish/fish.nix @@ -4,10 +4,7 @@ pkgs, ... }: -# fish for the primary user, configured natively through home-manager. Wires the -# done and bang-bang plugins, a fastfetch greeting, a bat-backed manpager, helper -# functions, the eza aliases, and vi-style command-line editing. Set fish as the -# default login shell by also turning on `modules.fish.defaultShell`. +# fish for the primary user, configured through home-manager. let cfg = config.modules.fish; user = config.user.name; @@ -24,28 +21,21 @@ in }; config = lib.mkIf cfg.enable { - # System-level fish: registers it in /etc/shells and installs vendor - # completions. + # System-level fish registers it in /etc/shells and installs vendor completions. programs.fish.enable = true; users.users.${user}.shell = lib.mkIf cfg.defaultShell pkgs.fish; home-manager.users.${user} = { home.packages = with pkgs; [ - eza # modern ls with git awareness and icons; backs the ls aliases - bat # syntax-highlighting cat/pager; backs the manpager below - fastfetch # system-info banner printed as the shell greeting - wget # non-interactive HTTP downloader; backs the wget abbreviation + eza # backs the ls/la/ll aliases + bat # backs the manpager + fastfetch # the shell greeting + wget # backs the wget abbreviation ]; programs.fish = { enable = true; - # Relied-on upstream defaults, pinned so a future change can't silently - # alter behaviour. - generateCompletions = true; - - # Prefer abbreviations over aliases when other modules wire up fish - # shortcuts, matching the abbreviation-first style below. preferAbbrs = true; plugins = [ @@ -94,7 +84,6 @@ in }; functions = { - # Run fastfetch as the welcome message. fish_greeting = "fastfetch"; history = { @@ -114,8 +103,7 @@ in }; }; - # Read from a real fish file, which home-manager renders into - # ~/.config/fish/config.fish. + # Rendered by home-manager into ~/.config/fish/config.fish. interactiveShellInit = builtins.readFile ./config.fish; }; }; diff --git a/modules/nvim/nvim.nix b/modules/nvim/nvim.nix index f261255..dc560e1 100644 --- a/modules/nvim/nvim.nix +++ b/modules/nvim/nvim.nix @@ -5,12 +5,8 @@ inputs, ... }: -# Neovim for the primary user, configured declaratively through nixvim. Options, -# keymaps, and plugin settings are typed Nix; the imperative remainder (the -# colorscheme call and the Neogit blame-toggle autocmd) lives in ./config.lua. -# Plugins come from nixpkgs — no plugin manager and no runtime cloning — and -# treesitter grammars are built by Nix, so no compiler is needed at runtime. git -# backs the git plugins; ripgrep and fd back the picker. +# Neovim for the primary user, configured declaratively through nixvim. The +# imperative remainder (colorscheme, Neogit blame autocmd) lives in ./config.lua. let cfg = config.modules.nvim; user = config.user.name; @@ -32,7 +28,7 @@ in ]; globals.mapleader = " "; - globals.clipboard = "osc52"; # neovim's built-in OSC 52 provider, no external binary needed + globals.clipboard = "osc52"; # built-in OSC 52 provider, no external binary needed opts = { clipboard = "unnamedplus"; @@ -180,8 +176,8 @@ in }; }; - # gbprod/nord.nvim; nixvim's colorschemes.nord is a different plugin. Set - # up in ./config.lua. + # gbprod/nord.nvim (nixvim's colorschemes.nord is a different plugin); + # set up in ./config.lua. extraPlugins = [ pkgs.vimPlugins.gbprod-nord ]; extraConfigLua = builtins.readFile ./config.lua; diff --git a/modules/tmux/tmux.nix b/modules/tmux/tmux.nix index 4c41605..2a2aa38 100644 --- a/modules/tmux/tmux.nix +++ b/modules/tmux/tmux.nix @@ -3,10 +3,8 @@ lib, ... }: -# tmux for the primary user, configured natively through home-manager. The -# settings home-manager exposes as options are set here; every setting it has -# no option for is read verbatim from ./extra.conf. No tmux plugin manager is -# used. +# tmux for the primary user, configured through home-manager. Settings without a +# home-manager option are read from ./extra.conf. let cfg = config.modules.tmux; user = config.user.name; @@ -21,8 +19,8 @@ in prefix = "C-Space"; keyMode = "vi"; mouse = true; - baseIndex = 1; # windows and panes count from 1. - clock24 = true; # 24-hour clock in the clock-mode overlay. + baseIndex = 1; + clock24 = true; escapeTime = 10; # short Esc delay so exiting insert mode in nvim isn't laggy. historyLimit = 10000; terminal = "tmux-256color"; diff --git a/system/default.nix b/system/default.nix index 2909c68..bda9581 100644 --- a/system/default.nix +++ b/system/default.nix @@ -5,13 +5,12 @@ inputs, ... }: -# The Skeleton's shared base config: the pieces every Host carries regardless of -# which Modules it enables — overlays, the `user`, flakes, and home-manager. +# Shared base config carried by every host. let inherit (lib) mkOption types; user = config.user; - # Instantiate an extra nixpkgs source for the same platform as the base pkgs. + # Args to instantiate an extra nixpkgs source on the base platform. pinArgs = prev: { inherit (prev.stdenv.hostPlatform) system; config.allowUnfree = true; @@ -23,7 +22,7 @@ in type = types.str; default = "alexion"; description = '' - The primary interactive user this Host is built for. Drives both the + The primary interactive user this host is built for. Drives both the system account and the home-manager user in lockstep. ''; }; @@ -35,9 +34,8 @@ in }; config = { - # Base is nixos-unstable; reach a package fresher with `unstable.` or - # pin it rock-solid with `stable.`. chaotic's overlay is added by its - # own NixOS module, imported by the host-builder. + # Reach fresher packages with `unstable.` or pin with `stable.`. + # chaotic's overlay is added by its own module, not here. nixpkgs.overlays = [ (_final: prev: { unstable = import inputs.nixpkgs-unstable (pinArgs prev); @@ -46,22 +44,21 @@ in ]; nixpkgs.config.allowUnfree = true; - # Flakes + a baseline so `nixos-rebuild switch` works from the console. + # Flakes, so `nixos-rebuild switch` works from the console. nix.settings.experimental-features = [ "nix-command" "flakes" ]; - # The chaotic binary cache, so the CachyOS kernel is substituted rather than - # compiled. Appended with the `extra-` options so cache.nixos.org and any - # other substituter are kept alongside it. + # chaotic's binary cache, so the CachyOS kernel is fetched rather than + # compiled. The `extra-` prefix keeps cache.nixos.org alongside it. nix.settings.extra-substituters = [ "https://nyx-cache.chaotic.cx/" ]; nix.settings.extra-trusted-public-keys = [ "nyx-cache.chaotic.cx:dJxTrgMC3V3cFfyIiBQDQorG6k1LsqurH/srpMSq7qk=" ]; environment.systemPackages = [ pkgs.git ]; - # Primary user, in wheel. No password is set here. + # Primary user, in the wheel group. No password set here. users.users.${user.name} = { isNormalUser = true; description = user.description; @@ -69,8 +66,8 @@ in }; # home-manager as a NixOS module: one `nixos-rebuild switch` builds the - # system and the user environment atomically, sharing the system's pkgs - # (with our overlays) and installing user packages into the system profile. + # system and user environment together, sharing the system's pkgs and + # installing user packages into the system profile. home-manager = { useGlobalPkgs = true; useUserPackages = true; -- 2.47.3 From 6a10f760cf7284fe18f5fd0fbb56b3afc92a5ba0 Mon Sep 17 00:00:00 2001 From: alexion Date: Sun, 19 Jul 2026 07:49:32 -0400 Subject: [PATCH 27/32] refactor(lib): remove the unused scriptFromFile helper It had no callers; the built system's derivation is unchanged by its removal. --- lib/default.nix | 6 ------ 1 file changed, 6 deletions(-) diff --git a/lib/default.nix b/lib/default.nix index 6318ebe..ca62fd5 100644 --- a/lib/default.nix +++ b/lib/default.nix @@ -65,17 +65,11 @@ let hostNames = attrNames (filterAttrs (_name: type: type == "directory") (builtins.readDir hostsDir)); in genAttrs hostNames (hostName: mkHost { inherit hostName; }); - - # --- Script-from-file helper -------------------------------------------- - # Turn a standalone script file into a package on PATH, keeping the script - # itself editable as a real file rather than an inlined heredoc. - scriptFromFile = pkgs: name: path: pkgs.writeShellScriptBin name (builtins.readFile path); in { inherit collectNixFiles mkHost mkHosts - scriptFromFile ; } -- 2.47.3 From 77e853ab49434ffe832a1713543a38310b71dc20 Mon Sep 17 00:00:00 2001 From: alexion Date: Sun, 19 Jul 2026 07:50:04 -0400 Subject: [PATCH 28/32] docs: rewrite the in-file comment convention Encode the sharpened ruleset: why-over-what, self-containment, the absence-only cross-file exception, no domain-glossary terms, no against-alternative or future-state framing, one-line file headers, placeholder wording, and how option description strings differ. --- CLAUDE.md | 14 ++++++++++---- 1 file changed, 10 insertions(+), 4 deletions(-) diff --git a/CLAUDE.md b/CLAUDE.md index 2ceb5a0..3c99aad 100644 --- a/CLAUDE.md +++ b/CLAUDE.md @@ -5,10 +5,16 @@ The domain model (Host, Module, Skeleton, Auto-loader, Enable convention, overla ## Conventions -- In-file comments describe only the current content and behaviour of the file they sit in. - Do not write comments about history ("used to be X", "now moved here"), about how a value is consumed in other files, or that justify the choice against alternatives. - Never reference agent-facing state (anything under `.claude/` or `CLAUDE.md`) from a code comment: that state is not part of understanding the code. - A reader looking at only that file should find every comment accurate and self-contained. +- Write comments only where they earn their place, and keep them concise. + Assume the reader can read code: comment the "why", not the "what", and explain "what" only when it is genuinely non-obvious. + A comment must be self-contained to its file — accurate to a reader looking at that file alone. + Do not write about history ("used to be X", "now moved here") or future state, about how a value is consumed elsewhere, or to justify the choice against alternatives; state the positive reason a thing exists, keeping any real stakes as a present-tense consequence. + The only permitted cross-file mention is a bare pointer explaining why something is *absent* here (e.g. "disko derives `fileSystems`; none declared here"), never narrating what the other file or tool does. + Do not use the domain model's capitalized terms (Host, Module, Skeleton, Auto-loader, Enable convention) as glossary references; describe things in plain language, using "host"/"module" only as ordinary lowercase nouns. + Never reference agent-facing state (anything under `.claude/` or `CLAUDE.md`). + A file-top header is one concise purpose line, added only where the filename or path does not already say it — never a feature inventory of the code below. + For a placeholder, say so plainly plus any actionable present-tense directive ("Placeholder: regenerate with nixos-generate-config on the target machine"), never "placeholder for ". + Option `description`/`mkEnableOption` strings are user-facing documentation rather than comments, so they may describe behaviour more fully — but the self-contained rule and the bans on glossary terms and agent-state references still apply. - 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.) -- 2.47.3 From 41709bb977f1a05721a709f3bbfebe60a4c0dcf9 Mon Sep 17 00:00:00 2001 From: alexion Date: Sun, 19 Jul 2026 08:01:49 -0400 Subject: [PATCH 29/32] chore!: remove the pre-NixOS dotfiles tree The flake and its modules now own every config these files provided; they remain in history for reference while the migration finishes. BREAKING CHANGE: `.config/`, `.gitconfig`, and `.github/` no longer exist at the repository root. A checkout that deploys this tree into `$HOME` will delete the live configs it previously installed. --- .claude/CLAUDE.md | 37 - .claude/hooks/attention-bell.sh | 21 - .claude/settings.json | 36 - .claude/skills/codebase-design/DEEPENING.md | 37 - .../skills/codebase-design/DESIGN-IT-TWICE.md | 44 - .claude/skills/codebase-design/SKILL.md | 113 -- .claude/skills/craft-skill/GLOSSARY.md | 195 ---- .claude/skills/craft-skill/SKILL.md | 49 - .claude/skills/domain-modeling/ADR-FORMAT.md | 47 - .../skills/domain-modeling/CONTEXT-FORMAT.md | 30 - .claude/skills/domain-modeling/SKILL.md | 56 - .claude/skills/grill/SKILL.md | 20 - .claude/skills/implement/SKILL.md | 72 -- .../skills/improve-codebase/HTML-REPORT.md | 119 -- .claude/skills/improve-codebase/SKILL.md | 68 -- .claude/skills/library/nbdev/SKILL.md | 152 --- .claude/skills/remove-skills/SKILL.md | 37 - .claude/skills/review-uncommitted/SKILL.md | 147 --- .claude/skills/setup-skills/LOCKFILE.md | 53 - .claude/skills/setup-skills/SKILL.md | 46 - .claude/skills/setup-skills/hash-dir.sh | 23 - .../skills/test-driven-development/SKILL.md | 153 --- .../skills/test-driven-development/mocking.md | 59 - .../test-driven-development/refactoring.md | 10 - .../skills/test-driven-development/tests.md | 77 -- .claude/skills/to-spec/SKILL.md | 21 - .claude/skills/to-spec/SPEC-FORMAT.md | 55 - .claude/skills/to-tasks/SKILL.md | 57 - .claude/skills/to-tasks/TASK-FORMAT.md | 28 - .claude/skills/update-skills/SKILL.md | 52 - .config/alacritty/alacritty.toml | 88 -- .config/dot/.claude/scheduled_tasks.lock | 1 - .config/dot/.claude/spec/dot-kde.md | 76 -- .config/dot/.claude/spec/dot-setup-folders.md | 59 - .config/dot/.claude/spec/dot-setup-nvim.md | 46 - .../dot/.claude/spec/gitea-axi-integration.md | 56 - .config/dot/.claude/spec/gitea-axi.md | 70 -- .../tasks/0000-nested-subcommand-discovery.md | 31 - .../tasks/0001-kde-schema-backed-save.md | 63 -- .../tasks/0002-kde-schema-backed-apply.md | 32 - .../tasks/0003-kde-schema-backed-diff.md | 35 - .../tasks/0004-kde-freeform-mechanism.md | 41 - .../tasks/0005-kde-shortcuts-mechanism.md | 57 - .../0006-setup-dispatcher-and-folders-core.md | 90 -- .../0007-folders-non-empty-confirmation.md | 52 - .../tasks/0008-folders-collision-handling.md | 55 - .../tasks/0009-kde-shortcut-completion.md | 51 - .../0010-kde-shortcuts-diff-broadscan.md | 102 -- .../tasks/0011-folders-unconditional-merge.md | 79 -- .../0012-folders-fixed-target-mapping.md | 64 -- .config/dot/CLAUDE.md | 167 --- .config/dot/commands/install.fish | 59 - .config/dot/commands/kde/kde.fish | 35 - .config/dot/commands/kde/kde.py | 535 --------- .config/dot/commands/setup/folders.fish | 134 --- .config/dot/commands/setup/setup.fish | 35 - .config/dot/commands/vpn.fish | 29 - .config/dot/kde-manifest | 32 - .config/dot/packages/pacman | 2 - .config/dot/tests/dot.fish | 1000 ----------------- .config/dot/tests/fixtures/kcfg/kwin.kcfg | 9 - .config/dot/tests/fixtures/kcfg/testrc.kcfg | 15 - .config/dot/tests/fixtures/kcfg/unmapped.kcfg | 9 - .config/fish/completions/bun.fish | 196 ---- .config/fish/completions/dot.fish | 34 - .config/fish/completions/fishtape.fish | 2 - .config/fish/conf.d/aliases.fish | 4 - .config/fish/conf.d/env.fish | 4 - .config/fish/conf.d/rustup.fish | 1 - .config/fish/config.fish | 15 - .config/fish/fish_plugins | 1 - .config/fish/fish_variables | 82 -- .config/fish/functions/dot.fish | 130 --- .config/fish/functions/fishtape.fish | 116 -- .config/nvim/after/ftplugin/markdown.lua | 1 - .config/nvim/init.lua | 3 - .config/nvim/lazy-lock.json | 13 - .config/nvim/lua/keys.lua | 8 - .config/nvim/lua/plugin.lua | 23 - .config/nvim/lua/plugins/git.lua | 36 - .config/nvim/lua/plugins/navigation.lua | 27 - .config/nvim/lua/plugins/ui.lua | 55 - .config/nvim/lua/vim_options.lua | 29 - .config/tmux/tmux.conf | 74 -- .config/user-dirs.dirs | 16 - .gitconfig | 3 - .github/README.md | 40 - .github/keybindings.md | 42 - .gitignore | 8 - 89 files changed, 6086 deletions(-) delete mode 100644 .claude/CLAUDE.md delete mode 100755 .claude/hooks/attention-bell.sh delete mode 100644 .claude/settings.json delete mode 100644 .claude/skills/codebase-design/DEEPENING.md delete mode 100644 .claude/skills/codebase-design/DESIGN-IT-TWICE.md delete mode 100644 .claude/skills/codebase-design/SKILL.md delete mode 100644 .claude/skills/craft-skill/GLOSSARY.md delete mode 100644 .claude/skills/craft-skill/SKILL.md delete mode 100644 .claude/skills/domain-modeling/ADR-FORMAT.md delete mode 100644 .claude/skills/domain-modeling/CONTEXT-FORMAT.md delete mode 100644 .claude/skills/domain-modeling/SKILL.md delete mode 100644 .claude/skills/grill/SKILL.md delete mode 100644 .claude/skills/implement/SKILL.md delete mode 100644 .claude/skills/improve-codebase/HTML-REPORT.md delete mode 100644 .claude/skills/improve-codebase/SKILL.md delete mode 100644 .claude/skills/library/nbdev/SKILL.md delete mode 100644 .claude/skills/remove-skills/SKILL.md delete mode 100644 .claude/skills/review-uncommitted/SKILL.md delete mode 100644 .claude/skills/setup-skills/LOCKFILE.md delete mode 100644 .claude/skills/setup-skills/SKILL.md delete mode 100755 .claude/skills/setup-skills/hash-dir.sh delete mode 100644 .claude/skills/test-driven-development/SKILL.md delete mode 100644 .claude/skills/test-driven-development/mocking.md delete mode 100644 .claude/skills/test-driven-development/refactoring.md delete mode 100644 .claude/skills/test-driven-development/tests.md delete mode 100644 .claude/skills/to-spec/SKILL.md delete mode 100644 .claude/skills/to-spec/SPEC-FORMAT.md delete mode 100644 .claude/skills/to-tasks/SKILL.md delete mode 100644 .claude/skills/to-tasks/TASK-FORMAT.md delete mode 100644 .claude/skills/update-skills/SKILL.md delete mode 100644 .config/alacritty/alacritty.toml delete mode 100644 .config/dot/.claude/scheduled_tasks.lock delete mode 100644 .config/dot/.claude/spec/dot-kde.md delete mode 100644 .config/dot/.claude/spec/dot-setup-folders.md delete mode 100644 .config/dot/.claude/spec/dot-setup-nvim.md delete mode 100644 .config/dot/.claude/spec/gitea-axi-integration.md delete mode 100644 .config/dot/.claude/spec/gitea-axi.md delete mode 100644 .config/dot/.claude/tasks/0000-nested-subcommand-discovery.md delete mode 100644 .config/dot/.claude/tasks/0001-kde-schema-backed-save.md delete mode 100644 .config/dot/.claude/tasks/0002-kde-schema-backed-apply.md delete mode 100644 .config/dot/.claude/tasks/0003-kde-schema-backed-diff.md delete mode 100644 .config/dot/.claude/tasks/0004-kde-freeform-mechanism.md delete mode 100644 .config/dot/.claude/tasks/0005-kde-shortcuts-mechanism.md delete mode 100644 .config/dot/.claude/tasks/0006-setup-dispatcher-and-folders-core.md delete mode 100644 .config/dot/.claude/tasks/0007-folders-non-empty-confirmation.md delete mode 100644 .config/dot/.claude/tasks/0008-folders-collision-handling.md delete mode 100644 .config/dot/.claude/tasks/0009-kde-shortcut-completion.md delete mode 100644 .config/dot/.claude/tasks/0010-kde-shortcuts-diff-broadscan.md delete mode 100644 .config/dot/.claude/tasks/0011-folders-unconditional-merge.md delete mode 100644 .config/dot/.claude/tasks/0012-folders-fixed-target-mapping.md delete mode 100644 .config/dot/CLAUDE.md delete mode 100644 .config/dot/commands/install.fish delete mode 100644 .config/dot/commands/kde/kde.fish delete mode 100644 .config/dot/commands/kde/kde.py delete mode 100644 .config/dot/commands/setup/folders.fish delete mode 100644 .config/dot/commands/setup/setup.fish delete mode 100644 .config/dot/commands/vpn.fish delete mode 100644 .config/dot/kde-manifest delete mode 100644 .config/dot/packages/pacman delete mode 100644 .config/dot/tests/dot.fish delete mode 100644 .config/dot/tests/fixtures/kcfg/kwin.kcfg delete mode 100644 .config/dot/tests/fixtures/kcfg/testrc.kcfg delete mode 100644 .config/dot/tests/fixtures/kcfg/unmapped.kcfg delete mode 100644 .config/fish/completions/bun.fish delete mode 100644 .config/fish/completions/dot.fish delete mode 100644 .config/fish/completions/fishtape.fish delete mode 100644 .config/fish/conf.d/aliases.fish delete mode 100644 .config/fish/conf.d/env.fish delete mode 100644 .config/fish/conf.d/rustup.fish delete mode 100644 .config/fish/config.fish delete mode 100644 .config/fish/fish_plugins delete mode 100644 .config/fish/fish_variables delete mode 100644 .config/fish/functions/dot.fish delete mode 100644 .config/fish/functions/fishtape.fish delete mode 100644 .config/nvim/after/ftplugin/markdown.lua delete mode 100644 .config/nvim/init.lua delete mode 100644 .config/nvim/lazy-lock.json delete mode 100644 .config/nvim/lua/keys.lua delete mode 100644 .config/nvim/lua/plugin.lua delete mode 100644 .config/nvim/lua/plugins/git.lua delete mode 100644 .config/nvim/lua/plugins/navigation.lua delete mode 100644 .config/nvim/lua/plugins/ui.lua delete mode 100644 .config/nvim/lua/vim_options.lua delete mode 100644 .config/tmux/tmux.conf delete mode 100644 .config/user-dirs.dirs delete mode 100644 .gitconfig delete mode 100644 .github/README.md delete mode 100644 .github/keybindings.md diff --git a/.claude/CLAUDE.md b/.claude/CLAUDE.md deleted file mode 100644 index 7c341e5..0000000 --- a/.claude/CLAUDE.md +++ /dev/null @@ -1,37 +0,0 @@ -# Alexion's Agent Instructions - -These are common instructions for Alexion's agents across all scenarios. - -## General Guidelines - -- When writing commit messages, NEVER auto-add your agent name as co-author. - Omit the `Co-Authored-By:` trailer entirely, with no exceptions. - This overrides any default instruction to append one. -- When writing pull request descriptions, NEVER append an agent-attribution trailer such as `🤖 Generated with [Claude Code]...`. - Leave it out entirely, with no exceptions. - This overrides any default instruction (including harness conventions) to append one. -- Never manually modify CHANGELOG.md files or any files that are marked as auto-generated. - Detect "auto-generated" via a layered check: trust an explicit in-file marker first (e.g. `AUTO-GENERATED, DO NOT EDIT`). - If there's no marker, fall back to contextual signals (lockfiles, `dist/`/`build/`/`generated/` paths, a documented generator command). - If it's still ambiguous, ask before editing rather than guessing. -- When writing or substantially editing long Markdown files, put each full sentence in its own line. - Preserve normal Markdown structure, but avoid wrapping multiple sentences onto one physical line. - Apply this to any prose you author, regardless of file length; "long" is not a real threshold. - Only format what you're actually writing or changing. - Never reflow an entire pre-existing paragraph or file just because you touched something nearby. -- When making technical decisions, do not give much weight to development cost. - Instead, prefer quality, simplicity, robustness, scalability and long term maintainability. - This is specifically about implementation time. - Human cost/benefit heuristics ("not worth N extra days of engineering") don't transfer to an AI agent that codes far faster than a human. - This is not a license to override standard anti-overengineering guardrails (avoid premature abstraction, no speculative config, etc.); those still apply to unnecessary complexity. - It means: don't discount a more robust or maintainable approach just because it would take a human a long time to build. -- File names should always be lower case, unless there's a valid reason. - Established ecosystem or tool conventions count as a valid reason automatically (e.g. `README.md`, `LICENSE`, `CHANGELOG.md`, `Makefile`, `Dockerfile`, `.github/` files), without needing to ask each time. -- When you discover that a belief you held about an objective fact or convention of the current project was wrong, write it down so it isn't relearned next time. - This applies whether the user corrected you or you caught the mistake yourself, and only to things that are true regardless of who is operating the project (a wrong build command, a wrong file path, a convention you guessed at instead of checking) — not personal working-style preferences or one-off task details. - Record it in that project's own CLAUDE.md, not this global file, under a dedicated `## Gotchas` section (create the section if the file doesn't have one yet). - If the project has nested CLAUDE.md files, use the one nearest to where the mistake occurred, falling back to the project's top-level CLAUDE.md. - Append to an existing CLAUDE.md immediately, without asking; if no CLAUDE.md exists yet for the project, ask before creating one. - Briefly mention the edit in your response rather than making it silently. - If an existing entry is later found to be wrong or stale, correct or remove it the same way. - diff --git a/.claude/hooks/attention-bell.sh b/.claude/hooks/attention-bell.sh deleted file mode 100755 index 0b83487..0000000 --- a/.claude/hooks/attention-bell.sh +++ /dev/null @@ -1,21 +0,0 @@ -#!/bin/sh -# attention-bell.sh — ring the terminal bell in this Claude session's tmux pane -# so tmux's monitor-bell flags the (background) window red in the status bar. -# -# Claude Code runs hooks as detached subprocesses: they have no controlling -# terminal, so /dev/tty is unavailable here. But the parent-process chain up to -# the `claude` process stays intact, and `claude` itself holds the pane's pty. -# So we walk ancestry to find it and write the bell straight to that tty. -# (Writing a bare BEL to an explicit /dev/pts/N works even from a detached -# process — verified against tmux's window_bell_flag.) - -pid=$PPID -while [ "$pid" -gt 1 ] 2>/dev/null; do - if [ "$(ps -o comm= -p "$pid" 2>/dev/null)" = claude ]; then - tty=$(ps -o tty= -p "$pid" 2>/dev/null | tr -d ' ') - [ -n "$tty" ] && [ "$tty" != '?' ] && printf '\a' > "/dev/$tty" - exit 0 - fi - pid=$(ps -o ppid= -p "$pid" 2>/dev/null | tr -d ' ') - [ -z "$pid" ] && break -done diff --git a/.claude/settings.json b/.claude/settings.json deleted file mode 100644 index d83714a..0000000 --- a/.claude/settings.json +++ /dev/null @@ -1,36 +0,0 @@ -{ - "hooks": { - "Stop": [ - { - "hooks": [ - { - "type": "command", - "command": "~/.claude/hooks/attention-bell.sh" - } - ] - } - ], - "Notification": [ - { - "hooks": [ - { - "type": "command", - "command": "~/.claude/hooks/attention-bell.sh" - } - ] - } - ], - "PreToolUse": [ - { - "matcher": "Write|Edit", - "hooks": [ - { - "type": "command", - "command": "f=$(jq -r '.tool_input.file_path // empty'); printf '%s' \"$f\" | grep -Eq '(\\.(test|spec)\\.[cm]?[jt]sx?$)|/__tests__/' && jq -n '{hookSpecificOutput:{hookEventName:\"PreToolUse\",additionalContext:\"test-driven-development skill: the MAIN agent must not author test files. Each test is written by a test-writer sub-agent (via the Agent tool) from the public interface alone. If you are running /implement or any TDD flow and have not loaded /test-driven-development, load it now and delegate this test to the sub-agent. If you ARE the test-writer sub-agent, disregard this reminder.\"}}' || true" - } - ] - } - ] - }, - "model": "opus" -} diff --git a/.claude/skills/codebase-design/DEEPENING.md b/.claude/skills/codebase-design/DEEPENING.md deleted file mode 100644 index 3938457..0000000 --- a/.claude/skills/codebase-design/DEEPENING.md +++ /dev/null @@ -1,37 +0,0 @@ -# Deepening - -How to deepen a cluster of shallow modules safely, given its dependencies. Assumes the vocabulary in [SKILL.md](SKILL.md) — **module**, **interface**, **seam**, **adapter**. - -## Dependency categories - -When assessing a candidate for deepening, classify its dependencies. The category determines how the deepened module is tested across its seam. - -### 1. In-process - -Pure computation, in-memory state, no I/O. Always deepenable — merge the modules and test through the new interface directly. No adapter needed. - -### 2. Local-substitutable - -Dependencies that have local test stand-ins (PGLite for Postgres, in-memory filesystem). Deepenable if the stand-in exists. The deepened module is tested with the stand-in running in the test suite. The seam is internal; no port at the module's external interface. - -### 3. Remote but owned (Ports & Adapters) - -Your own services across a network boundary (microservices, internal APIs). Define a **port** (interface) at the seam. The deep module owns the logic; the transport is injected as an **adapter**. Tests use an in-memory adapter. Production uses an HTTP/gRPC/queue adapter. - -Recommendation shape: *"Define a port at the seam, implement an HTTP adapter for production and an in-memory adapter for testing, so the logic sits in one deep module even though it's deployed across a network."* - -### 4. True external (Mock) - -Third-party services (Stripe, Twilio, etc.) you don't control. The deepened module takes the external dependency as an injected port; tests provide a mock adapter. - -## Seam discipline - -- **One adapter means a hypothetical seam. Two adapters means a real one.** Don't introduce a port unless at least two adapters are justified (typically production + test). A single-adapter seam is just indirection. -- **Internal seams vs external seams.** A deep module can have internal seams (private to its implementation, used by its own tests) as well as the external seam at its interface. Don't expose internal seams through the interface just because tests use them. - -## Testing strategy: replace, don't layer - -- Old unit tests on shallow modules become waste once tests at the deepened module's interface exist — delete them. -- Write new tests at the deepened module's interface. The **interface is the test surface**. -- Tests assert on observable outcomes through the interface, not internal state. -- Tests should survive internal refactors — they describe behaviour, not implementation. If a test has to change when the implementation changes, it's testing past the interface. diff --git a/.claude/skills/codebase-design/DESIGN-IT-TWICE.md b/.claude/skills/codebase-design/DESIGN-IT-TWICE.md deleted file mode 100644 index d41f678..0000000 --- a/.claude/skills/codebase-design/DESIGN-IT-TWICE.md +++ /dev/null @@ -1,44 +0,0 @@ -# Design It Twice - -When the user wants to explore alternative interfaces for a chosen deepening candidate, use this parallel sub-agent pattern. Based on "Design It Twice" (Ousterhout) — your first idea is unlikely to be the best. - -Uses the vocabulary in [SKILL.md](SKILL.md) — **module**, **interface**, **seam**, **adapter**, **leverage**. - -## Process - -### 1. Frame the problem space - -Before spawning sub-agents, write a user-facing explanation of the problem space for the chosen candidate: - -- The constraints any new interface would need to satisfy -- The dependencies it would rely on, and which category they fall into (see [DEEPENING.md](DEEPENING.md)) -- A rough illustrative code sketch to ground the constraints — not a proposal, just a way to make the constraints concrete - -Show this to the user, then immediately proceed to Step 2. The user reads and thinks while the sub-agents work in parallel. - -### 2. Spawn sub-agents - -Spawn 3+ sub-agents in parallel using the Agent tool. Each must produce a **radically different** interface for the deepened module. - -Prompt each sub-agent with a separate technical brief (file paths, coupling details, dependency category from [DEEPENING.md](DEEPENING.md), what sits behind the seam). The brief is independent of the user-facing problem-space explanation in Step 1. Give each agent a different design constraint: - -- Agent 1: "Minimize the interface — aim for 1–3 entry points max. Maximise leverage per entry point." -- Agent 2: "Maximise flexibility — support many use cases and extension." -- Agent 3: "Optimise for the most common caller — make the default case trivial." -- Agent 4 (if applicable): "Design around ports & adapters for cross-seam dependencies." - -Include both [SKILL.md](SKILL.md) vocabulary and `.claude/CONTEXT.md` vocabulary in the brief so each sub-agent names things consistently with the architecture language and the project's domain language. - -Each sub-agent outputs: - -1. Interface (types, methods, params — plus invariants, ordering, error modes) -2. Usage example showing how callers use it -3. What the implementation hides behind the seam -4. Dependency strategy and adapters (see [DEEPENING.md](DEEPENING.md)) -5. Trade-offs — where leverage is high, where it's thin - -### 3. Present and compare - -Present designs sequentially so the user can absorb each one, then compare them in prose. Contrast by **depth** (leverage at the interface), **locality** (where change concentrates), and **seam placement**. - -After comparing, give your own recommendation: which design you think is strongest and why. If elements from different designs would combine well, propose a hybrid. Be opinionated — the user wants a strong read, not a menu. diff --git a/.claude/skills/codebase-design/SKILL.md b/.claude/skills/codebase-design/SKILL.md deleted file mode 100644 index cdc3a74..0000000 --- a/.claude/skills/codebase-design/SKILL.md +++ /dev/null @@ -1,113 +0,0 @@ ---- -name: codebase-design -description: Shared vocabulary for designing deep modules. Use when the user wants to design or improve a module's interface, find deepening opportunities, decide where a seam goes, make code more testable or AI-navigable, or when another skill needs the deep-module vocabulary. ---- - -# Codebase Design - -Design **deep modules**: a lot of behaviour behind a small interface, placed at a clean seam, testable through that interface. Use this language and these principles wherever code is being designed or restructured. The aim is leverage for callers, locality for maintainers, and testability for everyone. - -## Glossary - -Use these terms exactly — don't substitute "component," "service," "API," or "boundary." Consistent language is the whole point. - -**Module** — anything with an interface and an implementation. Deliberately scale-agnostic: a function, class, package, or tier-spanning slice. _Avoid_: unit, component, service. - -**Interface** — everything a caller must know to use the module correctly: the type signature, but also invariants, ordering constraints, error modes, required configuration, and performance characteristics. _Avoid_: API, signature (too narrow — they refer only to the type-level surface). - -**Implementation** — what's inside a module, its body of code. Distinct from **Adapter**: a thing can be a small adapter with a large implementation (a Postgres repository) or a large adapter with a small implementation (an in-memory fake). Reach for "adapter" when the seam is the topic; "implementation" otherwise. - -**Depth** — leverage at the interface: the amount of behaviour a caller (or test) can exercise per unit of interface they have to learn. A module is **deep** when a large amount of behaviour sits behind a small interface, **shallow** when the interface is nearly as complex as the implementation. - -**Seam** _(Michael Feathers)_ — a place where you can alter behaviour without editing in that place; the *location* at which a module's interface lives. Where to put the seam is its own design decision, distinct from what goes behind it. _Avoid_: boundary (overloaded with DDD's bounded context). - -**Adapter** — a concrete thing that satisfies an interface at a seam. Describes *role* (what slot it fills), not substance (what's inside). - -**Leverage** — what callers get from depth: more capability per unit of interface they learn. One implementation pays back across N call sites and M tests. - -**Locality** — what maintainers get from depth: change, bugs, knowledge, and verification concentrate in one place rather than spreading across callers. Fix once, fixed everywhere. - -## Deep vs shallow - -**Deep module** = small interface + lots of implementation: - -``` -┌─────────────────────┐ -│ Small Interface │ ← Few methods, simple params -├─────────────────────┤ -│ │ -│ Deep Implementation│ ← Complex logic hidden -│ │ -└─────────────────────┘ -``` - -**Shallow module** = large interface + little implementation (avoid): - -``` -┌─────────────────────────────────┐ -│ Large Interface │ ← Many methods, complex params -├─────────────────────────────────┤ -│ Thin Implementation │ ← Just passes through -└─────────────────────────────────┘ -``` - -When designing an interface, ask: - -- Can I reduce the number of methods? -- Can I simplify the parameters? -- Can I hide more complexity inside? - -## Principles - -- **Depth is a property of the interface, not the implementation.** A deep module can be internally composed of small, mockable, swappable parts — they just aren't part of the interface. A module can have **internal seams** (private to its implementation, used by its own tests) as well as the **external seam** at its interface. -- **The deletion test.** Imagine deleting the module. If complexity vanishes, it was a pass-through. If complexity reappears across N callers, it was earning its keep. -- **The interface is the test surface.** Callers and tests cross the same seam. If you want to test *past* the interface, the module is probably the wrong shape. -- **One adapter means a hypothetical seam. Two adapters means a real one.** Don't introduce a seam unless something actually varies across it. - -## Designing for testability - -Good interfaces make testing natural: - -1. **Accept dependencies, don't create them.** - - ```typescript - // Testable - function processOrder(order, paymentGateway) {} - - // Hard to test - function processOrder(order) { - const gateway = new StripeGateway(); - } - ``` - -2. **Return results, don't produce side effects.** - - ```typescript - // Testable - function calculateDiscount(cart): Discount {} - - // Hard to test - function applyDiscount(cart): void { - cart.total -= discount; - } - ``` - -3. **Small surface area.** Fewer methods = fewer tests needed. Fewer params = simpler test setup. - -## Relationships - -- A **Module** has exactly one **Interface** (the surface it presents to callers and tests). -- **Depth** is a property of a **Module**, measured against its **Interface**. -- A **Seam** is where a **Module**'s **Interface** lives. -- An **Adapter** sits at a **Seam** and satisfies the **Interface**. -- **Depth** produces **Leverage** for callers and **Locality** for maintainers. - -## Rejected framings - -- **Depth as ratio of implementation-lines to interface-lines** (Ousterhout): rewards padding the implementation. We use depth-as-leverage instead. -- **"Interface" as the TypeScript `interface` keyword or a class's public methods**: too narrow — interface here includes every fact a caller must know. - -## Going deeper - -- **Deepening a cluster given its dependencies** — see [DEEPENING.md](DEEPENING.md): dependency categories, seam discipline, and replace-don't-layer testing. -- **Exploring alternative interfaces** — see [DESIGN-IT-TWICE.md](DESIGN-IT-TWICE.md): spin up parallel sub-agents to design the interface several radically different ways, then compare on depth, locality, and seam placement. diff --git a/.claude/skills/craft-skill/GLOSSARY.md b/.claude/skills/craft-skill/GLOSSARY.md deleted file mode 100644 index 26d756d..0000000 --- a/.claude/skills/craft-skill/GLOSSARY.md +++ /dev/null @@ -1,195 +0,0 @@ -# Glossary — Building Great Skills - -The domain model for what makes a skill great. A skill exists to wrangle determinism out of a stochastic system; the root virtue is **Predictability**, and every term below is a lever on it. This is the disclosed reference for [`craft-skill`](SKILL.md). - -The terms are grouped by axis: **Invocation** (how a skill is reached), **Information Hierarchy** (how its content is arranged), **Steering** (how the agent's runtime behaviour is shaped), and **Pruning** (how it is kept lean). Each **failure mode** lives beside the lever that cures it, tagged _failure mode_. - -**Bold terms** in any definition are themselves defined in this glossary; find them by their heading. - -## Predictability - -The degree to which a skill makes the agent behave the same _way_ on every run — the same process, not the same output (a brainstorming skill should _predictably_ diverge; its tokens vary, its behaviour doesn't). The root virtue every other term serves — cost and maintainability are symptoms of it, not rivals. - -_Avoid_: consistency, reliability, robustness, output-determinism - -## Invocation - -How a skill is reached — and the two loads you pay for the choice. - -### Model-Invoked - -A skill that keeps its **description** field, so the agent can see it and fire it autonomously — and the human can still type its name, so model-invocation always _includes_ user reach. There is no model-only state: a description only ever _adds_ agent discovery, never removes the human's. Pays a permanent **context load** on every turn in exchange for that discoverability. Reachable by other skills, because the description that makes it agent-discoverable makes it invocable. A model-invoked skill whose content is all **reference** is also one home for shared reference: another skill can invoke it, so reference needed by several skills lives in one place. Pick model-invocation only when the agent must reach the skill on its own; if it never fires except by hand, drop the description and pay no context load. - -_Avoid_: ability, tool, capability - -### User-Invoked - -A skill with its **description** stripped — invisible to the agent and reachable only by the human typing its name (user-_only_, where **model-invoked** is user-_and-agent_). Trades agent-discoverability for zero **context load**. Because it has no description, nothing but the human can reach it: no other skill can fire it. - -_Avoid_: procedure, workflow, command - -### Description - -The skill's machine-readable trigger, and the one **context pointer** a **model-invoked** skill is forced to keep loaded at all times. Its mere presence _is_ the invocation axis: keep it and the skill is model-invoked (and reachable by other skills); delete it and the skill is **user-invoked**, reachable only by the human. The source of a model-invoked skill's **context load**. - -_Avoid_: frontmatter, summary - -### Context Pointer - -A reference held in the agent's context that names some out-of-context material and encodes the condition for reaching it. The **description** is the top-level context pointer (context window → skill); pointers to disclosed files are the same object one level down. Its wording, not the target, decides _when_ the agent reaches — and _how reliably_. A must-have target behind a weakly worded pointer is a variance bug: fix the wording first, and inline the material only if sharpening fails. - -_Avoid_: link, reference, import - -### Context Load - -The cost a **model-invoked** skill imposes on the agent's context window — its **description**, always loaded, spending both tokens and attention. What **user-invoked** skills escape by having no description, and the brake on splitting into more model-invoked skills. - -_Avoid_: token cost, context bloat - -### Cognitive Load - -The cost a **user-invoked** skill imposes on the human — what they must hold in their head: which skills exist and when to reach for each (the human is the index). What **model-invocation** removes by being agent-discoverable, and the brake on splitting into more user-invoked skills. Not a cost to minimise: it is the price of human agency, the reason some skills stay user-invoked. Spend it where human judgement matters; remove it where it does not. - -_Avoid_: human index, burden, overhead - -### Router Skill - -A **user-invoked** skill whose job is to point at your other user-invoked skills — naming each and when to reach for it — so the human has one skill to remember instead of many. It can only hint, never fire them: user-invoked skills have no **description**, so nothing but the human can reach them. The cure for **cognitive load** when user-invoked skills multiply. - -_Avoid_: dispatcher, menu, registry, index, router procedure - -### Granularity - -How finely you divide skills. Finer division spends one of the two loads: more **model-invoked** skills spend **context load** (more descriptions crowding the window and competing for attention); more **user-invoked** skills spend **cognitive load** (more for the human to remember and reach for). Two cuts guide the division. By **invocation**, split off a model-invoked skill where you have a distinct **leading word** to trigger it — a trigger word you actually use in your prompts. By **sequence**, split a run of **steps** where a step's **post-completion steps** need hiding, since isolating it in its own context clears what follows. Beware the reverse: merging sequences exposes each step's post-completion steps to what follows, inviting premature completion. - -_Avoid_: chunking, modularity - -## Information Hierarchy - -How a skill's content is arranged, and how far down the ladder each piece sits. - -### Information Hierarchy - -A skill's content ranked by how immediately the agent needs it — a single ladder, produced by two cuts: in-file or behind a pointer, and step or reference. The rungs: - -- **Steps** — in-file, primary -- **Reference**, in-file — secondary -- **Reference**, disclosed — behind a **context pointer** - -A skill with no **steps** uses just the bottom two rungs — often a legitimately flat peer-set (e.g. every rule of a review on one rung), which is a fine arrangement, not a smell. The hierarchy is independent of invocation: a skill can be model- or user-invoked whether it is all steps, all reference, or both. When a skill has steps, in-file reference that should be disclosed buries them and turns attending to them into a coin-flip — a variance lever, not just a legibility one. Keep the top of the ladder legible; push down it whatever you can. - -_Avoid_: structure, organization, layout - -### Steps - -The ordered actions the agent performs — when a skill has them, the primary tier of its content, and the part that earns its place in SKILL.md. Not every skill has steps: a skill can be all steps (`test-driven-development`), all **reference** (a review), or both, independent of invocation. Every step ends on a **completion criterion**, clear or vague. - -_Avoid_: workflow, instructions, choreography - -### Reference - -Material the agent refers to on demand — definitions, facts, parameters, examples, conditional instructions. When a skill has **steps** it is secondary to them; when a skill has none it is the entire content; or it lives outside any skill entirely — see **External Reference**. Reached via **context pointers**, and the prime candidate for **progressive disclosure**. - -_Avoid_: supporting material, docs, background - -### External Reference - -**Reference** that lives outside the skill system — a plain file, no **description**, no **steps**, not invocable — that any skill can point at. The home for shared reference that needn't fire on its own, and the only shared home two **user-invoked** skills can use, since neither has a description and so neither can fire the other. - -_Avoid_: doc, resource, knowledge base - -### Progressive Disclosure - -Moving **reference** down the ladder — out of SKILL.md and behind a **context pointer** — so the top stays legible. Not primarily a token optimisation; it is how the **information hierarchy** is protected. Licensed by **branching**: disclose what only some branches need, inline what every path needs, and if a pointer fires unreliably on must-have material, sharpen its wording, and pull it back inline only if that fails. - -_Avoid_: lazy loading, chunking - -### Co-location - -Keeping the material an agent needs at once in one place — a concept's definition, rules, and caveats under a single heading, not scattered across the file — so reading one part brings its neighbours with it. The within-file companion to the **Information Hierarchy**: the hierarchy ranks _how far down_ a piece sits; co-location decides _what sits beside it_ once there. There is no formula for the right format of a body of **reference**; the test is that a skill should read like documentation written for the agent, and grouped material reads that way where scattered material does not. Distinct from **Duplication**: that repeats one meaning in two places, where scattering fragments a single meaning across many. - -_Avoid_: grouping, clustering, cohesion - -### Sprawl - -_Failure mode._ A skill that is simply too long — too many lines in SKILL.md — independent of whether they are stale or repeated. Even an all-live, all-unique skill can sprawl. It costs readability (the agent wades through more before it can act, and attention thins across the excess), maintainability (every extra line is one more to keep **relevant**), and tokens. The cure is the **information hierarchy**: push **reference** down behind **context pointers**, and split by **branch** or sequence so each path carries only what it needs. Distinct from **sediment** (length from stale accumulation) and **duplication** (length from repeated meaning) — sprawl is length itself, whatever its cause. - -_Avoid_: bloat, length, size, verbosity - -## Steering - -The levers that shape the agent's runtime behaviour toward **Predictability**. - -### Branch - -A distinct way a skill can be invoked — a case the skill handles — so different runs take different paths through it. A skill with many steps may carry many branches; a linear one has none. - -_Avoid_: path, case, fork - -### Leading Word - -A compact concept — also called a _Leitwort_ — already living in the model's pretraining, that the agent thinks with while running the skill. It encodes a behavioural principle in the fewest possible tokens by invoking priors the model already holds (e.g. _lesson_, _proximal zone of development_, _fog of war_, _tracer bullets_). Repeated as a token, never as a sentence, it accumulates a distributed definition across the skill and anchors a whole region of behaviour. Coining your own works if you define it clearly, but a made-up word recruits no priors — you pay in definition tokens what a pretrained word gives free. Reach for an existing word first. - -A leading word serves **predictability** twice. In the body it anchors **execution** — the agent reaches for the same behaviour every time the concept appears, and inside flat reference it focuses attention on a class of thing to look for, recruiting the right checks each run. In the **description** it anchors **invocation** — and not only within the skill: when the same word lives in your prompts, your docs, and your codebase, the agent links that shared language to the skill and fires it more reliably. Word a description with the leading words you actually use when you want the skill. - -_Avoid_: keyword, term, motif - -### Completion Criterion - -The condition that tells the agent a unit of work is done — the target it judges against. Two properties make it a lever, not just a quality. Its **clarity** (can the agent tell done from not-done?) resists **premature completion** — a vague bound ("understanding reached") lets the agent declare done and slip to the next step; this axis needs _steps_ to bite, since premature completion is a between-steps failure. Its **demand** (how much it requires) sets **legwork** — "every modified model accounted for" forces thorough work where "produce a change list" does not — and this axis is _not_ step-bound: it can bind a body of flat reference too, which is how a skill with no steps still carries an exhaustiveness bar ("every rule applied"). The strongest criteria are both checkable and exhaustive. - -_Avoid_: done condition, exit condition, stopping rule - -### Legwork - -The work an agent does behind the scenes within a single step — reading files, exploring the codebase, making changes, digging up what it needs rather than offloading to the user. It lives below the step structure: never written as its own step, latent in the wording, controlled by the agent rather than the skill. The within-step counterpart to **post-completion steps**' across-step pull. Raised by a **leading word** (_comprehensive_, _thorough_) or a **completion criterion** that demands the work be exhaustive — including the demand axis applied to flat reference, which is what drives a skill of flat reference to cover all its rungs. Goes thin either when that demand is missing or when **premature completion** cuts the step short. - -_Avoid_: scope, effort, diligence, coverage - -### Post-Completion Steps - -The **steps** that follow the current step. Visible, they pull the agent forward into **premature completion** — the more it sees, the stronger the tug; the defence is to hide them by splitting the sequence of steps into two. - -_Avoid_: horizon, fog of war, lookahead - -### Premature Completion - -_Failure mode._ Ending the current step before it is genuinely done, because the agent's attention slips to being done rather than to the work. A between-steps failure: it needs **steps** to occur — a skill with no steps that quits early isn't premature completion but thin **legwork** under an unmet demand. A tug-of-war between two forces: visible **post-completion steps** (the pull forward) and the **completion criterion**'s clarity (the resistance — a sharp, checkable bar holds; a vague one gives way). Fuzziness is the necessary condition: a sharp bound resists the pull no matter how many later steps are visible, so a step that never rushes needs no defending. Two levers hold a step that does, but reach for them in order: **sharpen the bound first** — it is local and cheap. Only when the criterion is irreducibly fuzzy _and_ you actually observe the rush do you **hide the later steps** — and hiding only works across a real context boundary (a user-invoked hand-off or a subagent dispatch; an inline model-invoked call leaves the later steps in context and clears nothing). One cause of thin legwork, but distinct from it: legwork can be thin even when a step runs to full completion. - -_Avoid_: premature closure, the rush, rushing, shortcutting - -## Pruning - -Keeping a skill lean — each remedy paired with the failure it cures. - -### Single Source of Truth - -The desired state where each meaning lives in exactly one authoritative place, so a change to the skill's behaviour is a change in one place. **Duplication** is its violation. - -_Avoid_: home, canonical location - -### Duplication - -_Failure mode._ The same meaning given more than one **single source of truth**. It costs maintenance (change one place, you must change the others), costs tokens, and inflates prominence — repeating a meaning weights it on the ladder past its real rank. The accidental inverse of a **leading word**, which raises attention on purpose by repeating a token, never the meaning. - -_Avoid_: repetition, redundancy - -### Relevance - -Whether a line still bears on what the skill does — the lens for what to keep. A line loses relevance either by never bearing on the task (mere exposition, or a **branch** that should be disclosed) or by going stale: drifting out of date as the behaviour or world it describes changes. Shorter skills are easier to keep relevant, because each line is cheaper to check. Distinct from **no-op**: relevance asks whether a line bears on the task, not whether it changes behaviour. - -_Avoid_: load-bearing, staleness, freshness - -### Sediment - -_Failure mode._ Layers of old content that settle in a skill and are never cleared, because adding feels safe and removing feels risky — so stale and irrelevant lines accumulate and you must core down through them to find what is still live. The default fate of any skill without a pruning discipline; the slow erosion of **relevance**, as opposed to **duplication**'s repeated meaning. - -_Avoid_: accretion, bloat, cruft, rot - -### No-Op - -_Failure mode._ An instruction that changes nothing because the model already does it by default — you pay load to tell the agent what it would do anyway. The test: does a line change behaviour versus the default? A line can be perfectly **relevant** and still be a no-op. The same priors that make a **leading word** free make a no-op worthless. - -A leading word is a _technique_; No-Op is a _verdict_ on a line — and they cross. A leading word too weak to beat the default is a no-op (_be thorough_ when the agent is already thorough-ish), and the fix is a stronger word that passes the verdict (_relentless_), not a different technique. So the No-Op test — does it change behaviour versus the default? — is also how you grade whether a leading word is earning its repetitions. This is model-relative, not reader-relative: two people disagreeing over whether a line is a no-op disagree about the default, and settle it by running the skill, not by debate. - -_Avoid_: redundant instruction, restating the obvious, belaboring diff --git a/.claude/skills/craft-skill/SKILL.md b/.claude/skills/craft-skill/SKILL.md deleted file mode 100644 index 7b7f78c..0000000 --- a/.claude/skills/craft-skill/SKILL.md +++ /dev/null @@ -1,49 +0,0 @@ ---- -name: craft-skill -description: Draft a new skill, or audit and rewrite an existing one, judged against the vocabulary in GLOSSARY.md. -disable-model-invocation: true ---- - -Draft a new skill from scratch, or audit and rewrite an existing one — both judged against one bar: **predictability**, the agent taking the same process every run. **Bold terms** are defined in [`GLOSSARY.md`](GLOSSARY.md); look them up there for the full meaning. - -This skill does not judge general prose quality (clarity, jargon, sentence structure) — only skill-specific structure. A dedicated technical-writing-guide skill will cover the former once it exists; until then, use your own judgment for sentence-level prose. - -## Which branch - -If the request describes a new workflow, capability, or repeated manual process with no existing skill named — **Draft a new skill**. If it names an existing skill (by name or path) to review, fix, or improve — **Audit an existing skill**. Both end at **Verify and ship**. - -## Draft a new skill - -1. **Capture intent.** If the conversation already contains the workflow (e.g. "turn this into a skill"), extract answers from it first; only ask about what's missing. Ask one question at a time — several at once is bewildering: - - What should this make the agent do — is it a **procedure** (ordered **steps**), **knowledge** it consults (**reference**), or both? This decides the shape from the start. - - When would you actually reach for it: do you type its name, or should the agent reach for it unprompted? Walk the **context load** vs **cognitive load** tradeoff explicitly rather than defaulting — see `Invocation` in GLOSSARY.md. - - Does it have distinct **branches** — cases that take different paths? Name each. A linear checklist's items aren't branches by themselves — look for actual alternate paths, not the steps that always all run. - - Is this workflow already documented somewhere in the project (a README, CLAUDE.md, CONTRIBUTING)? If so the draft should point there rather than restate it — see `External Reference` and `Single Source of Truth` in GLOSSARY.md. - - For each step, what does done look like — a **completion criterion** you could check without ambiguity? - - Is there already a word — in your prompts, docs, or codebase — that names this behavior? Reach for that **leading word** before coining one. - Done when every axis above has an answer, or the user says to just draft something and iterate. - -2. **Write the draft.** First decide where it lives: project-local `.claude/skills/` if the workflow is tied to this one repo, `~/.claude/skills/` if it's general-purpose across projects. Then follow the **information hierarchy**: steps for what the agent does in order, in-file **reference** for facts every branch needs, and disclose the rest behind a pointer — to a sibling file, or to the existing project docs identified in step 1 rather than restating them. Done when every branch from step 1 has somewhere to live, and no sentence fails the no-op test in isolation (see `No-Op` in GLOSSARY.md). - -## Audit an existing skill - -1. **Locate it.** Check the current project's `.claude/skills/`, then `~/.claude/skills/`, then `~/.claude/skills/library/`, in that order; ask if the name is ambiguous across locations. If it's tracked in a project's `skills-lock.yaml`, mention that editing it here will make it read as locally-customized to `update-skills` — confirm that's actually intended rather than editing the library source. - -2. **Apply the checklist.** Read the skill and its disclosed files, then check each against GLOSSARY.md, quoting the offending line for anything that fails: - - **Premature completion** — is each completion criterion checkable, and does it demand what the step actually needs? - - **Duplication** — does any meaning appear in more than one place? - - **Sediment** — any line that no longer bears on what the skill does? - - **Sprawl** — could in-file reference be disclosed instead, or a run of steps split by branch? - - **No-op** — any sentence the model would already do by default? Test sentence by sentence, not line by line — a line can carry one load-bearing sentence and one no-op sentence together. - - Is the **invocation** choice (model- vs user-invoked) still the right one for how this skill actually gets used? Is there a restated concept that should collapse into a **leading word**? - -3. **Rewrite** based on the findings. Done when every finding from step 2 is either addressed or explicitly noted as intentionally kept. - -## Verify and ship - -1. Propose one realistic test prompt — reflecting the trigger phrasing gathered (draft) or the skill's existing purpose (audit) — and get it confirmed or adjusted before spending a run on it. -2. Spawn one subagent: give it the skill's path and the confirmed prompt, have it attempt the task using the skill, and report back what happened — including anywhere it hesitated, misread the skill, or did something unexpected. -3. Re-read the draft/rewrite against GLOSSARY.md's failure modes in light of that run, and fix whatever either pass turned up. If the fix is substantial, repeat from step 1; otherwise it's done. -4. Stage the specific changed or created paths — one path per file, never a wildcard — with the host project's own staging convention: plain `git add ` normally, or e.g. `dot add ` in this dotfiles setup (wrap as `fish -c "dot add "` if the invoking shell isn't fish — `dot` is a fish function, not a binary on `$PATH`). Do not commit; that's left to the user. - -Done when the subagent's run succeeded without confusion on the confirmed prompt, the checklist raised nothing outstanding, and every changed path is staged. diff --git a/.claude/skills/domain-modeling/ADR-FORMAT.md b/.claude/skills/domain-modeling/ADR-FORMAT.md deleted file mode 100644 index 15c134e..0000000 --- a/.claude/skills/domain-modeling/ADR-FORMAT.md +++ /dev/null @@ -1,47 +0,0 @@ -# ADR Format - -ADRs live in `.claude/adr/` and use sequential numbering: `0001-slug.md`, `0002-slug.md`, etc. - -Create the `.claude/adr/` directory lazily — only when the first ADR is needed. - -## Template - -```md -# {Short title of the decision} - -{1-3 sentences: what's the context, what did we decide, and why.} -``` - -That's it. An ADR can be a single paragraph. The value is in recording *that* a decision was made and *why* — not in filling out sections. - -## Optional sections - -Only include these when they add genuine value. Most ADRs won't need them. - -- **Status** frontmatter (`proposed | accepted | deprecated | superseded by ADR-NNNN`) — useful when decisions are revisited -- **Considered Options** — only when the rejected alternatives are worth remembering -- **Consequences** — only when non-obvious downstream effects need to be called out - -## Numbering - -Scan `.claude/adr/` for the highest existing number and increment by one. - -## When to offer an ADR - -All three of these must be true: - -1. **Hard to reverse** — the cost of changing your mind later is meaningful -2. **Surprising without context** — a future reader will look at the code and wonder "why on earth did they do it this way?" -3. **The result of a real trade-off** — there were genuine alternatives and you picked one for specific reasons - -If a decision is easy to reverse, skip it — you'll just reverse it. If it's not surprising, nobody will wonder why. If there was no real alternative, there's nothing to record beyond "we did the obvious thing." - -### What qualifies - -- **Architectural shape.** "We're using a monorepo." "The write model is event-sourced, the read model is projected into Postgres." -- **Integration patterns between contexts.** "Ordering and Billing communicate via domain events, not synchronous HTTP." -- **Technology choices that carry lock-in.** Database, message bus, auth provider, deployment target. Not every library — just the ones that would take a quarter to swap out. -- **Boundary and scope decisions.** "Customer data is owned by the Customer context; other contexts reference it by ID only." The explicit no-s are as valuable as the yes-s. -- **Deliberate deviations from the obvious path.** "We're using manual SQL instead of an ORM because X." Anything where a reasonable reader would assume the opposite. These stop the next engineer from "fixing" something that was deliberate. -- **Constraints not visible in the code.** "We can't use AWS because of compliance requirements." "Response times must be under 200ms because of the partner API contract." -- **Rejected alternatives when the rejection is non-obvious.** If you considered GraphQL and picked REST for subtle reasons, record it — otherwise someone will suggest GraphQL again in six months. diff --git a/.claude/skills/domain-modeling/CONTEXT-FORMAT.md b/.claude/skills/domain-modeling/CONTEXT-FORMAT.md deleted file mode 100644 index f256d20..0000000 --- a/.claude/skills/domain-modeling/CONTEXT-FORMAT.md +++ /dev/null @@ -1,30 +0,0 @@ -# CONTEXT.md Format - -## Structure - -```md -# {Context Name} - -{One or two sentence description of what this context is and why it exists.} - -## Language - -**Order**: -{A one or two sentence description of the term} -_Avoid_: Purchase, transaction - -**Invoice**: -A request for payment sent to a customer after delivery. -_Avoid_: Bill, payment request - -**Customer**: -A person or organization that places orders. -_Avoid_: Client, buyer, account -``` - -## Rules - -- **Be opinionated.** When multiple words exist for the same concept, pick the best one and list the others under `_Avoid_`. -- **Keep definitions tight.** One or two sentences max. Define what it IS, not what it does. -- **Only include terms specific to this project's context.** General programming concepts (timeouts, error types, utility patterns) don't belong even if the project uses them extensively. Before adding a term, ask: is this a concept unique to this context, or a general programming concept? Only the former belongs. -- **Group terms under subheadings** when natural clusters emerge. If all terms belong to a single cohesive area, a flat list is fine. diff --git a/.claude/skills/domain-modeling/SKILL.md b/.claude/skills/domain-modeling/SKILL.md deleted file mode 100644 index e2a2b9e..0000000 --- a/.claude/skills/domain-modeling/SKILL.md +++ /dev/null @@ -1,56 +0,0 @@ ---- -name: domain-modeling -description: Build and sharpen a project's domain model. Use when the user wants to pin down domain terminology or a ubiquitous language, record an architectural decision, or when another skill needs to maintain the domain model. ---- - -# Domain Modeling - -Actively build and sharpen the project's domain model as you design. This is the *active* discipline — challenging terms, inventing edge-case scenarios, and writing the glossary and decisions down the moment they crystallise. (Merely *reading* `.claude/CONTEXT.md` for vocabulary is not this skill — that's a one-line habit any skill can do. This skill is for when you're changing the model, not just consuming it.) - -## File structure - -``` -/ -├── .claude/ -│ ├── CONTEXT.md -│ └── adr/ -│ ├── 0001-event-sourced-orders.md -│ └── 0002-postgres-for-write-model.md -└── src/ -``` - -Create files lazily — only when you have something to write. If no `.claude/CONTEXT.md` exists, create it when the first term is resolved. If no `.claude/adr/` exists, create it when the first ADR is needed. - -## During the session - -### Challenge against the glossary - -When the user uses a term that conflicts with the existing language in `.claude/CONTEXT.md`, call it out immediately. "Your glossary defines 'cancellation' as X, but you seem to mean Y — which is it?" - -### Sharpen fuzzy language - -When the user uses vague or overloaded terms, propose a precise canonical term. "You're saying 'account' — do you mean the Customer or the User? Those are different things." - -### Discuss concrete scenarios - -When domain relationships are being discussed, stress-test them with specific scenarios. Invent scenarios that probe edge cases and force the user to be precise about the boundaries between concepts. - -### Cross-reference with code - -When the user states how something works, check whether the code agrees. If you find a contradiction, surface it: "Your code cancels entire Orders, but you just said partial cancellation is possible — which is right?" - -### Update .claude/CONTEXT.md inline - -When a term is resolved, update `.claude/CONTEXT.md` right there. Don't batch these up — capture them as they happen. Use the format in [CONTEXT-FORMAT.md](./CONTEXT-FORMAT.md). - -`.claude/CONTEXT.md` should be totally devoid of implementation details. Do not treat `.claude/CONTEXT.md` as a spec, a scratch pad, or a repository for implementation decisions. It is a glossary and nothing else. - -### Offer ADRs sparingly - -Only offer to create an ADR when all three are true: - -1. **Hard to reverse** — the cost of changing your mind later is meaningful -2. **Surprising without context** — a future reader will wonder "why did they do it this way?" -3. **The result of a real trade-off** — there were genuine alternatives and you picked one for specific reasons - -If any of the three is missing, skip the ADR. Use the format in [ADR-FORMAT.md](./ADR-FORMAT.md). diff --git a/.claude/skills/grill/SKILL.md b/.claude/skills/grill/SKILL.md deleted file mode 100644 index 7431d71..0000000 --- a/.claude/skills/grill/SKILL.md +++ /dev/null @@ -1,20 +0,0 @@ ---- -name: grill -description: Interview the user relentlessly about a plan or design, capturing the resolved terms and decisions into the project's domain model as you go if one exists. Use when the user wants to stress-test a plan before building, or uses any 'grill' trigger phrase. ---- - -Interview me relentlessly about every aspect of this plan or design. Walk down each branch of the design tree, resolving dependencies between decisions one by one, and give your recommended answer for each question. Keep going until every branch carries an explicit decision and no dependency between decisions is left open — not merely until it feels like "we understand each other." - -Ask the questions one at a time, waiting for feedback on each before continuing. Asking several at once is bewildering. - -If a question can be answered by exploring the codebase, explore the codebase instead of asking it. - -**Never start implementation during or after the interview without an explicit instruction from the user.** This applies at every point — mid-interview and after the final question alike. - -## Closing the interview - -When every branch carries an explicit decision and no dependency is left open, produce a concise summary of all decisions reached, then stop and wait for the user's next instruction. - -## Tracking the domain model as you go - -If a `.claude/CONTEXT.md` file exists in the project, also run [`domain-modeling`](../domain-modeling/SKILL.md) alongside this interview: resolve each term into `.claude/CONTEXT.md` the moment it crystallizes, and offer an ADR using that skill's own criteria — hard to reverse, surprising without context, and the result of a real trade-off. If no `.claude/CONTEXT.md` exists, run the interview alone with no doc side effects. diff --git a/.claude/skills/implement/SKILL.md b/.claude/skills/implement/SKILL.md deleted file mode 100644 index 726131d..0000000 --- a/.claude/skills/implement/SKILL.md +++ /dev/null @@ -1,72 +0,0 @@ ---- -name: implement -description: Implement a task file produced by /to-tasks on its own branch, review it, close it out, and open a PR. -disable-model-invocation: true ---- - -Implement a task file end-to-end: branch, build it, review it, close it out, and open a PR. - -## Process - -### 1. Read the task file and check blockers - -The user passes the path to a task file (`.claude/tasks/-slug.md`, as produced by `/to-tasks`) explicitly — don't infer one from context. - -If the task's frontmatter has a `blocked-by` field, read each referenced task file and check for any unresolved `- [ ]` acceptance criterion. If any blocker isn't fully resolved, warn the user which one and why, and confirm before proceeding — don't refuse outright. - -### 2. Sync `main` and branch off it - -Switch to `main`, fast-forward it (`git pull --ff-only`), then create and switch to a branch named `task--` — taken verbatim from the task file's basename, so `.claude/tasks/0003-issue-view-and-truncation.md` gives `task-0003-issue-view-and-truncation`. -Use whatever git invocation the project itself uses; a repo may wrap it. - -Stop and ask the user before going further if: - -- **The working tree has uncommitted changes.** Never stash them automatically. -- **`git pull --ff-only` fails.** Local `main` has diverged; report what diverged. Never `reset --hard`. -- **The task's `blocked-by` work isn't reachable from `main`.** The blocker's PR is likely unmerged; name it. - -If the task branch already exists, switch to it and carry on — don't recreate it, and don't rebase it onto the freshly pulled `main`. -Always branch off `main`, never off a sibling task branch. - -### 3. Implement - -Build the work described in the task's "What to build" section, satisfying its acceptance criteria. ALWAYS use `/test-driven-development`, at the seams already agreed when the spec or task was written. - -Run typechecking regularly, single test files regularly, and the full test suite once at the end. - -### 4. Stage the changes - -Stage (`git add`) each file you create or modify, specifically — not `git add -A` — so nothing untracked and unrelated gets swept in. - -### 5. Review - -Run `/review-uncommitted`, passing the task file itself as the spec source — it already links back to its parent spec via its `spec` frontmatter field, if any. Address anything it raises before moving on. - -Keep its report — step 7 puts part of it in the PR. - -### 6. Close out the task file - -Mark every acceptance criterion `[x]` if satisfied or `[-]` if deliberately dropped, so none are left `[ ]`. Append a `## Implementation Notes` section explaining any deviations from the plan — dropped criteria (referencing which, and why), scope changes, decisions made mid-implementation, follow-ups worth flagging. Skip the section only if nothing deviated. Leave the `spec` and `blocked-by` frontmatter fields untouched — they're a permanent record, not a checklist to clear (see `to-tasks`'s `TASK-FORMAT.md`). - -Stage the updated task file with the rest. - -### 7. Commit, push, and open a PR - -Make **one** commit for the whole task, code and task file together. -Match the repo's existing commit convention — read its recent history or its CLAUDE.md, don't assume one — and reference the task in the subject, e.g. `(task 0003)`. - -Push the branch (`git push -u origin task--`) and open a pull request against `main` with the repo's forge CLI: `tea` for Gitea, `gh` for GitHub. -Never base the PR on a sibling task branch. -Open it ready, not draft. - -The PR body carries: - -- The task file's path. -- A short summary of what was built, and any deviations — the same ones just written into `## Implementation Notes`. -- A `## Review` section: the `## Risk` block from step 5 verbatim (overall rating plus its six factor lines), then **only** the Standards and Spec findings left unaddressed, each with a one-line reason. Findings that were fixed are already in the diff; leave them out. - -Don't ask for confirmation before pushing or opening the PR. -If the repo has no remote, stop after the commit and report that no PR was opened. - -Stay on the task branch when done. -Report the branch name, the PR URL, and any unaddressed review findings. diff --git a/.claude/skills/improve-codebase/HTML-REPORT.md b/.claude/skills/improve-codebase/HTML-REPORT.md deleted file mode 100644 index 731bd96..0000000 --- a/.claude/skills/improve-codebase/HTML-REPORT.md +++ /dev/null @@ -1,119 +0,0 @@ -# HTML Report Format - -The architectural review is rendered as a single self-contained HTML file in the OS temp directory. Tailwind and Mermaid both come from CDNs. Mermaid handles graph-shaped diagrams reliably; hand-built divs and inline SVG handle the more editorial visuals (mass diagrams, cross-sections). Mix the two — don't lean on Mermaid for everything, it'll start to look generic. - -## Scaffold - -```html - - - - - Architecture review — {{repository name}} - - - - - -
-
...
-
...
-
...
-
- - -``` - -## Header - -Repo name, date, and a compact legend: solid box = module, dashed line = seam, red arrow = leakage, thick dark box = deep module. No introduction paragraph — straight into the candidates. - -## Candidate card - -The diagrams carry the weight. Prose is sparse, plain, and uses the glossary terms (from the `/codebase-design` skill) without ceremony. - -Each candidate is one `
`: - -- **Title** — short, names the deepening (e.g. "Collapse the Order intake pipeline"). -- **Badge row** — recommendation strength (`Strong` = emerald, `Worth exploring` = amber, `Speculative` = slate), plus a tag for the dependency category (`in-process`, `local-substitutable`, `ports & adapters`, `mock`). -- **Files** — monospaced list, `font-mono text-sm`. -- **Before / After diagram** — the centrepiece. Two columns, side by side. See patterns below. -- **Problem** — one sentence. What hurts. -- **Solution** — one sentence. What changes. -- **Wins** — bullets, ≤6 words each. e.g. "Tests hit one interface", "Pricing logic stops leaking", "Delete 4 shallow wrappers". -- **ADR callout** (if applicable) — one line in an amber-tinted box. - -No paragraphs of explanation. If the diagram needs a paragraph to be understood, redraw the diagram. - -## Diagram patterns - -Pick the pattern that fits the candidate. Mix them. Don't make every diagram look the same — variety is part of the point. - -### Mermaid graph (the workhorse for dependencies / call flow) - -Use a Mermaid `flowchart` or `graph` when the point is "X calls Y calls Z, and look at the mess." Wrap it in a Tailwind-styled card so it doesn't feel parachuted in. Style with classDef to colour leakage edges red and the deep module dark. Sequence diagrams work well for "before: 6 round-trips; after: 1." - -```html -
-
-    flowchart LR
-      A[OrderHandler] --> B[OrderValidator]
-      B --> C[OrderRepo]
-      C -.leak.-> D[PricingClient]
-      classDef leak stroke:#dc2626,stroke-width:2px;
-      class C,D leak
-  
-
-``` - -### Hand-built boxes-and-arrows (when Mermaid's layout fights you) - -Modules as `
`s with borders and labels. Arrows as inline SVG `` or `` elements positioned absolutely over a relative container. Reach for this when you want the "after" diagram to feel like one thick-bordered deep module with greyed-out internals — Mermaid won't render that with the right weight. - -### Cross-section (good for layered shallowness) - -Stack horizontal bands (`h-12 border-l-4`) to show layers a call passes through. Before: 6 thin layers each doing nothing. After: 1 thick band labelled with the consolidated responsibility. - -### Mass diagram (good for "interface as wide as implementation") - -Two rectangles per module — one for interface surface area, one for implementation. Before: interface rectangle is nearly as tall as the implementation rectangle (shallow). After: interface rectangle is short, implementation rectangle is tall (deep). - -### Call-graph collapse - -Before: a tree of function calls rendered as nested boxes. After: the same tree collapsed into one box, with the now-internal calls shown faded inside it. - -## Style guidance - -- Lean editorial, not corporate-dashboard. Generous whitespace. Serif optional for headings (`font-serif` works well with stone/slate). -- Colour sparingly: one accent (emerald or indigo) plus red for leakage and amber for warnings. -- Keep diagrams ~320px tall so before/after sits comfortably side by side without scrolling. -- Use `text-xs uppercase tracking-wider` for module labels inside diagrams — they should read as schematic, not as UI. -- The only scripts are the Tailwind CDN and the Mermaid ESM import. The report is otherwise static — no app code, no interactivity beyond Mermaid's own rendering. - -## Top recommendation section - -One larger card. Candidate name, one sentence on why, anchor link to its card. That's it. - -## Tone - -Plain English, concise — but the architectural nouns and verbs come straight from the `/codebase-design` glossary, terms and exclusions alike. Concision is not an excuse to drift. - -**Phrasings that fit the style:** - -- "Order intake module is shallow — interface nearly matches the implementation." -- "Pricing leaks across the seam." -- "Deepen: one interface, one place to test." -- "Two adapters justify the seam: HTTP in prod, in-memory in tests." - -**Wins bullets** name the gain in glossary terms: *"locality: bugs concentrate in one module"*, *"leverage: one interface, N call sites"*, *"interface shrinks; implementation absorbs the wrappers"*. Don't write *"easier to maintain"* or *"cleaner code"* — those terms aren't in the glossary and don't earn their place. - -No hedging, no throat-clearing, no "it's worth noting that…". If a sentence could be a bullet, make it a bullet. If a bullet could be cut, cut it. If a term isn't in the `/codebase-design` glossary, reach for one that is before inventing a new one. diff --git a/.claude/skills/improve-codebase/SKILL.md b/.claude/skills/improve-codebase/SKILL.md deleted file mode 100644 index 7e58837..0000000 --- a/.claude/skills/improve-codebase/SKILL.md +++ /dev/null @@ -1,68 +0,0 @@ ---- -name: improve-codebase -description: Scan a codebase for deepening opportunities, present them as a visual HTML report, then grill through whichever one you pick. -disable-model-invocation: true ---- - -# Improve Codebase - -Surface architectural friction and propose **deepening opportunities** — refactors that turn shallow modules into deep ones. The aim is testability and AI-navigability. - -This command is _informed_ by the project's domain model and built on a shared design vocabulary: - -- Run the `/codebase-design` skill for the architecture vocabulary (**module**, **interface**, **depth**, **seam**, **adapter**, **leverage**, **locality**) and its principles (the deletion test, "the interface is the test surface", "one adapter = hypothetical seam, two = real"). Use its terms exactly in every suggestion, per its glossary. -- The domain language in `.claude/CONTEXT.md` gives names to good seams; ADRs in `.claude/adr/` record decisions this command should not re-litigate. - -## Process - -### 1. Explore - -Read the project's domain glossary (`.claude/CONTEXT.md`) and any ADRs in the area you're touching first. - -Then use the Agent tool with `subagent_type=Explore` to walk every top-level module or directory in scope (the whole repository, or the area the user pointed you to) — even if only briefly for the ones that turn out clean. Within each, judge friction organically rather than against a rigid checklist: - -- Where does understanding one concept require bouncing between many small modules? -- Where are modules **shallow** — interface nearly as complex as the implementation? -- Where have pure functions been extracted just for testability, but the real bugs hide in how they're called (no **locality**)? -- Where do tightly-coupled modules leak across their seams? -- Which parts of the codebase are untested, or hard to test through their current interface? - -Apply the **deletion test** to anything you suspect is shallow: would deleting it concentrate complexity, or just move it? A "yes, concentrates" is the signal you want. - -Zero candidates is a legitimate outcome for a genuinely clean area — but it has to follow from having looked, not from stopping early. - -### 2. Present candidates as an HTML report - -Write a self-contained HTML file to the OS temp directory so nothing lands in the repository. Resolve the temp dir from `$TMPDIR`, falling back to `/tmp` (or `%TEMP%` on Windows), and write to `/architecture-review-.html` so each run gets a fresh file. Open it for the user — `xdg-open ` on Linux, `open ` on macOS, `start ` on Windows. Treat the open as best-effort: it's a no-op in a headless/sandboxed environment with no display server, so report the absolute path regardless of whether the open succeeded. - -The report uses **Tailwind via CDN** for layout and styling, and **Mermaid via CDN** for diagrams where a graph/flow/sequence reliably communicates the structure. Mix Mermaid with hand-crafted CSS/SVG visuals — use Mermaid when relationships are graph-shaped (call graphs, dependencies, sequences), and hand-built divs/SVG when you want something more editorial (mass diagrams, cross-sections, collapse animations). Each candidate gets a **before/after visualisation**. Be visual. - -For each candidate, render a card with: - -- **Files** — which files/modules are involved -- **Problem** — why the current architecture is causing friction -- **Solution** — plain English description of what would change -- **Benefits** — explained in terms of locality and leverage, and how tests would improve -- **Before / After diagram** — side-by-side, custom-drawn, illustrating the shallowness and the deepening -- **Recommendation strength** — one of `Strong`, `Worth exploring`, `Speculative`, rendered as a badge - -End the report with a **Top recommendation** section: which candidate you'd tackle first and why. - -**Use `.claude/CONTEXT.md` vocabulary for the domain, and the `/codebase-design` vocabulary for the architecture.** If `.claude/CONTEXT.md` defines "Order," talk about "the Order intake module" — not "the FooBarHandler," and not "the Order service." - -**ADR conflicts**: if a candidate contradicts an existing ADR, only surface it when the friction is real enough to warrant revisiting the ADR. Mark it clearly in the card (e.g. a warning callout: _"contradicts ADR-0007 — but worth reopening because…"_). Don't list every theoretical refactor an ADR forbids. - -See [HTML-REPORT.md](HTML-REPORT.md) for the full HTML scaffold, diagram patterns, and styling guidance. - -Do NOT propose interfaces yet. After the file is written, ask the user: "Which of these would you like to explore?" - -### 3. Grilling loop - -Once the user picks a candidate, run `/grill` to walk the design tree with them — constraints, dependencies, the shape of the deepened module, what sits behind the seam, what tests survive. - -Side effects happen inline as decisions crystallize — run `/domain-modeling` to keep the domain model current as you go, even if `.claude/CONTEXT.md` doesn't exist yet: - -- **Naming a deepened module after a concept not in `.claude/CONTEXT.md`?** Add the term to `.claude/CONTEXT.md`. Create the file lazily if it doesn't exist. -- **Sharpening a fuzzy term during the conversation?** Update `.claude/CONTEXT.md` right there. -- **User rejects the candidate with a load-bearing reason?** Offer an ADR, framed as: _"Want me to record this as an ADR so future architecture reviews don't re-suggest it?"_ Only offer when the reason would actually be needed by a future explorer to avoid re-suggesting the same thing — skip ephemeral reasons ("not worth it right now") and self-evident ones. -- **Want to explore alternative interfaces for the deepened module?** Run the `/codebase-design` skill and use its design-it-twice parallel sub-agent pattern. diff --git a/.claude/skills/library/nbdev/SKILL.md b/.claude/skills/library/nbdev/SKILL.md deleted file mode 100644 index 3aeeb3a..0000000 --- a/.claude/skills/library/nbdev/SKILL.md +++ /dev/null @@ -1,152 +0,0 @@ ---- -name: nbdev -description: nbdev conventions for notebooks — directives, cell structure, docments, tests, execution. Use for any .ipynb operation — including reads — in an nbdev project. ---- - -# nbdev - -## Tool Preference - -- Use the **Jupyter MCP** for all `.ipynb` operations — read, edit, insert, delete, execute -- Do **not** use the built-in `NotebookEdit` tool; it writes cell source as a single JSON string which breaks standard Jupyter formatting and produces noisy diffs -- Re-read the notebook before editing if it may have changed since your last read — cell indices/IDs can shift under concurrent edits (e.g. via JupyterLab's real-time collaboration), and editing by a stale index can hit the wrong cell - -## nbdev Directives - -Directives are comments at the top of a cell that control how nbdev processes it: - -- `#| export` — include this cell in the exported Python module and in the docs -- `#| hide` — exclude this cell from both the module and the docs -- `#| hide_input` — show cell output in docs but hide the source code -- `#| default_exp module_name` — set which module this notebook exports to (second cell) -- `#| exporti` — export to module but do not show in docs (for internal helpers) -- `#| eval: false` — include in docs but do not execute during `nbdev-test` - -Imports needed only for tests or examples should **not** be exported. - -Never hand-edit the exported `.py` module files — they're build artifacts regenerated from the notebook by `nbdev_export`. All edits go through the source notebook in `nbs/`. - -## Notebook Structure - -Every notebook must follow this structure: - -**Cell 1 — Markdown frontmatter:** -```markdown -# Module Title - -> A one-line description of what this module does -``` -The H1 becomes the page title in docs. The blockquote becomes the subtitle. - -**Cell 2 — Default export:** -```python -#| default_exp module_name -``` - -**Body cells** — alternating between exported code, demonstrations, and markdown explanations (see Cell Structure below). - -**Last cell:** -```python -#| hide -import nbdev; nbdev.nbdev_export() -``` - -Before declaring any notebook task complete, restart the kernel and run all cells top-to-bottom to verify it is fully reproducible. - -## Cell Structure - -Keep cells short. Each exported function gets its own cell, immediately followed by a demonstration. Do not write long functions with comments interspersed — split them into small separate cells with explanations and working examples after each. - -The pattern per concept: - -1. *(Optional)* A markdown cell explaining what comes next -2. A `#| export` code cell with the function -3. One or more plain code cells demonstrating usage -4. Assertions that double as tests - -Example: -```python -#| export -def slugify(text: str) -> str: - "Convert text to a URL-safe slug" - return re.sub(r"[^a-z0-9]+", "-", text.lower()).strip("-") -``` -```python -slug = slugify("Hello, World!") -assert slug == "hello-world" -slug -``` - -## Docstrings and Parameter Documentation - -Keep docstrings short — a single-line summary is sufficient for most functions. Elaborate in separate markdown or code cells below, where you can use real examples. - -Use **docments** (inline parameter comments) instead of verbose docstring parameter sections: - -```python -#| export -def greet( - name: str, # Person to greet - greeting: str="Hi", # Greeting word to use -) -> str: # The composed greeting - "Compose a greeting for name" - return f"{greeting}, {name}!" -``` - -This renders as a clean parameter table in the docs automatically — no need to repeat type information in the docstring body. - -Use backticks around symbol names in docstrings and markdown — nbdev automatically converts these to hyperlinks to the relevant reference page. - -## Code Style - -- **Prefer composition**: write small functions that do one thing well -- Each exported function should be focused enough to fit naturally in a single notebook cell — one cell, one idea -- Use type hints on all exported functions -- Avoid classes unless state is genuinely needed — prefer functions that take and return data -- If you do write a class, use `fastcore`'s `@patch` decorator to define each method in its own cell, immediately followed by a demonstration. This avoids long class definitions and keeps examples close to the code - -When a class is needed, document its methods with `show_doc`: -```python -from nbdev.showdoc import show_doc -show_doc(MyClass.my_method) -``` - -## Tests - -Every code cell is run as a test by nbdev unless explicitly marked otherwise — any exception fails the test. - -- Turn demonstrations into tests by adding `assert` statements -- Use `fastcore.test` helpers for better error messages: - ```python - from fastcore.test import test_eq, test_fail - test_eq(slugify("Hello World"), "hello-world") - ``` -- Document expected error cases with `test_fail`: - ```python - test_fail(lambda: slugify(""), contains="empty") - ``` -- Each test/demo cell should import what it needs directly — don't rely on a name imported in a later cell just because it happened to be in scope during a prior run - -## Execution - -- Always execute cells after writing them to verify they work -- If a cell errors, read the full traceback before attempting a fix — do not guess -- When installing packages, use `%pip install` inside the notebook (not `!pip install`) so they install into the running kernel -- Use autoreload at the top of notebooks that import from other modules in the project: - ```python - %load_ext autoreload - %autoreload 2 - ``` - -## Documentation - -- Use H2 (`##`) markdown cells to group related symbols within a notebook -- Use H4 (`####`) markdown cells to split long explanations within a symbol's section (notes, examples, edge cases, etc.) -- Add rich representations to classes via `_repr_markdown_` where it aids understanding -- Include real code examples, plots, and diagrams — notebooks support rich output, use it - -## Outputs - -- Never print secrets, tokens, passwords, or API keys into cell output — notebook outputs get committed to git and published in docs, unlike transient script output -- Prefer summaries over dumping large data structures (`.head()`, `len()`, `[:5]`, etc.) -- Large outputs consume context window — keep them concise diff --git a/.claude/skills/remove-skills/SKILL.md b/.claude/skills/remove-skills/SKILL.md deleted file mode 100644 index e23ea0f..0000000 --- a/.claude/skills/remove-skills/SKILL.md +++ /dev/null @@ -1,37 +0,0 @@ ---- -name: remove-skills -description: Remove one or more previously added library skills from the current project. -disable-model-invocation: true ---- - -Removes a skill that [`setup-skills`](../setup-skills/SKILL.md) previously -copied into the current project, deleting both its files and its entry in -`.claude/skills-lock.yaml` (see [LOCKFILE.md](../setup-skills/LOCKFILE.md) -for its schema). - -## Steps - -1. Read `.claude/skills-lock.yaml`. If it doesn't exist or is empty, tell - the user there's nothing installed to remove and stop. - -2. Determine which skill(s) to remove: - - If the user's invocation already named a specific skill, use that — - if it isn't in the lockfile, say so and stop. - - Otherwise, list every skill currently in the lockfile and ask the - user to pick one (or more). - -3. For each skill to remove, compute its current hash - (`~/.claude/skills/setup-skills/hash-dir.sh .claude/skills/`) - and compare it to the hash stored in the lockfile: - - If it matches (never modified since it was installed), delete - `.claude/skills//` and remove its lockfile entry immediately — - no confirmation needed, since nothing of the user's is being lost. - - If it differs (locally customized), tell the user it has local - changes that will be permanently lost and ask for confirmation - before deleting. If they decline, leave that skill installed and - move on to the next. - -4. Finish with a summary of what was removed and what was left in place. - -Done when every skill to remove has been either deleted (with its lockfile -entry removed) or explicitly left in place with a stated reason. diff --git a/.claude/skills/review-uncommitted/SKILL.md b/.claude/skills/review-uncommitted/SKILL.md deleted file mode 100644 index f9d2841..0000000 --- a/.claude/skills/review-uncommitted/SKILL.md +++ /dev/null @@ -1,147 +0,0 @@ ---- -name: review-uncommitted -description: Review the working tree's uncommitted changes along three axes — change risk, repo standards, and spec fidelity — using parallel sub-agents. ---- - -Three-axis review of the diff between `HEAD` and the working tree: - -- **Risk** — how much attention does this change warrant, from low to high? -- **Standards** — does the code conform to this repo's documented coding standards? -- **Spec** — does the code faithfully implement the originating PRD or task file? - -All three axes run as **parallel sub-agents** so they don't pollute each other's context, then this skill aggregates their findings. - -## Process - -### 1. Capture the diff - -The diff command is `git diff HEAD` — everything uncommitted, staged or not. -New files must already be tracked (`git add`ed) to show up; this skill doesn't scan for untracked files, so that's the caller's responsibility. - -Confirm the diff is non-empty before going further. -An empty diff should fail here — not inside three parallel sub-agents. - -### 2. Identify the spec source - -Look for the originating spec, in this order: - -1. A path the user passed as an argument. -2. A spec file matching the branch name or feature — `.claude/spec/.md`. -3. If nothing is found, ask the user where the spec is. - If they say there isn't one, the **Spec** sub-agent will skip and report "no spec available". - -### 3. Identify the standards sources - -Anything in the repo that documents how code should be written, such as `CODING_STANDARDS.md` or `CONTRIBUTING.md`. - -On top of whatever the repo documents, the Standards axis always carries the **smell baseline** below — a fixed set of Fowler code smells (_Refactoring_, ch.3) that applies even when a repo documents nothing. -Two rules bind it: - -- **The repo overrides.** - A documented repo standard always wins; where it endorses something the baseline would flag, suppress the smell. -- **Always a judgement call.** - Each smell is a labelled heuristic ("possible Feature Envy"), never a hard violation — and, like any standard here, skip anything tooling already enforces. - -Each smell reads *what it is* → *how to fix*; match it against the diff: - -- **Mysterious Name** — a function, variable, or type whose name doesn't reveal what it does or holds. - → rename it; if no honest name comes, the design's murky. -- **Duplicated Code** — the same logic shape appears in more than one hunk or file in the change. - → extract the shared shape, call it from both. -- **Feature Envy** — a method that reaches into another object's data more than its own. - → move the method onto the data it envies. -- **Data Clumps** — the same few fields or params keep travelling together (a type wanting to be born). - → bundle them into one type, pass that. -- **Primitive Obsession** — a primitive or string standing in for a domain concept that deserves its own type. - → give the concept its own small type. -- **Repeated Switches** — the same `switch`/`if`-cascade on the same type recurs across the change. - → replace with polymorphism, or one map both sites share. -- **Shotgun Surgery** — one logical change forces scattered edits across many files in the diff. - → gather what changes together into one module. -- **Divergent Change** — one file or module is edited for several unrelated reasons. - → split so each module changes for one reason. -- **Speculative Generality** — abstraction, parameters, or hooks added for needs the spec doesn't have. - → delete it; inline back until a real need shows. -- **Message Chains** — long `a.b().c().d()` navigation the caller shouldn't depend on. - → hide the walk behind one method on the first object. -- **Middle Man** — a class or function that mostly just delegates onward. - → cut it, call the real target direct. -- **Refused Bequest** — a subclass or implementer that ignores or overrides most of what it inherits. - → drop the inheritance, use composition. - -### 4. Risk rubric - -The Risk axis judges the diff alone — no repo-doc lookup, no input from the Standards or Spec sub-agents. -It always runs; it only needs the diff from step 1. - -Rate each of these six factors **Low / Medium / High**, then take the single highest-rated factor as the overall rating (worst-factor-wins): - -- **Blast radius** — isolated change vs. ripples across many files, modules, or callers. -- **Reversibility** — trivial rollback vs. hard to undo (migrations, deletions, published API/schema changes). -- **Test coverage** — covered by tests in/around the diff vs. untested. -- **Sensitive domain** — touches auth, security, payments, permissions, concurrency, or data migrations. -- **Size & complexity** — large diff or tangled control flow vs. small/simple. -- **Runtime criticality** — hot path/production-critical vs. internal or dev-only tooling. - -### 5. Spawn all three sub-agents in parallel - -Send a single message with three `Agent` tool calls. -Use the `general-purpose` subagent for all three. - -**Risk sub-agent prompt** — include: - -- The full diff (output of `git diff HEAD`). -- The six risk factors from step 4, pasted in full. -- The brief: "Rate each of the six factors Low/Medium/High with a one-clause reason, then give the overall rating as the highest of the six. - Report the overall rating first, then the six factor lines. - Under 200 words." - -**Standards sub-agent prompt** — include: - -- The full diff (output of `git diff HEAD`). -- The list of standards-source files you found in step 3, **plus the smell baseline from step 3** pasted in full — the sub-agent has no other access to it. -- The brief: "Report — per file/hunk where relevant — (a) every place the diff violates a documented standard: cite the standard (file + the rule); and (b) any baseline smell you spot: name it and quote the hunk. - Distinguish hard violations from judgement calls — documented-standard breaches can be hard, but baseline smells are always judgement calls, and a documented repo standard overrides the baseline. - Skip anything tooling enforces. - Under 400 words." - -**Spec sub-agent prompt** — include: - -- The full diff (output of `git diff HEAD`). -- The path or fetched contents of the spec. -- The brief: "Report: (a) requirements the spec asked for that are missing or partial; (b) behaviour in the diff that wasn't asked for (scope creep); (c) requirements that look implemented but where the implementation looks wrong. - Quote the spec line for each finding. - Under 400 words." - -If the spec is missing, skip the Spec sub-agent and note this in the final report. - -### 6. Aggregate - -Present the Risk report first, under a `## Risk` heading, with the overall rating bolded on its own line followed by the six factor lines: - -``` -## Risk -**Overall: HIGH** -- Blast radius: ... -- Reversibility: ... -- Test coverage: ... -- Sensitive domain: ... -- Size & complexity: ... -- Runtime criticality: ... -``` - -Then present the Standards and Spec reports under `## Standards` and `## Spec` headings, verbatim or lightly cleaned. -Do **not** merge or rerank findings — the axes are deliberately separate (see _Why Standards and Spec stay separate_). - -End with a one-line summary: total findings per axis (Standards/Spec only), and the worst issue _within each axis_ (if any). -Don't pick a single winner across axes — that's the reranking the separation exists to prevent. -The risk rating isn't repeated here; it already leads the report. - -## Why Standards and Spec stay separate - -A change can pass one axis and fail the other: - -- Code that follows every standard but implements the wrong thing → **Standards pass, Spec fail.** -- Code that does exactly what the PRD or task asked but breaks the project's conventions → **Spec pass, Standards fail.** - -Reporting them separately stops one axis from masking the other. diff --git a/.claude/skills/setup-skills/LOCKFILE.md b/.claude/skills/setup-skills/LOCKFILE.md deleted file mode 100644 index 3ffa79d..0000000 --- a/.claude/skills/setup-skills/LOCKFILE.md +++ /dev/null @@ -1,53 +0,0 @@ -# Skills Lockfile - -`.claude/skills-lock.yaml`, at the root of a project, tracks which library -skills (from `~/.claude/skills/library/`) have been copied into that -project's `.claude/skills/`, so [`setup-skills`](SKILL.md), -[`update-skills`](../update-skills/SKILL.md), and -[`remove-skills`](../remove-skills/SKILL.md) all agree on what's installed -without re-deriving it from the filesystem. - -## Schema - -A YAML list of entries, one per installed skill: - -```yaml -- name: nbdev - hash: 3f2a9b8c1d4e5f6a7b8c9d0e1f2a3b4c5d6e7f8a9b0c1d2e3f4a5b6c7d8e9f0a -- name: terraform-conventions - hash: 9c1d4e5f6a7b8c9d0e1f2a3b4c5d6e7f8a9b0c1d2e3f4a5b6c7d8e9f0a3f2a9b -``` - -- `name` — matches both the skill's directory name in the library - (`skills/library/`) and its copied directory name in the project - (`.claude/skills/`). -- `hash` — the output of `hash-dir.sh` run against that one skill's - directory contents, recorded at the moment it was last copied or - confirmed up to date. Never a hash of anything else — not the whole - project, not the whole library, just that one skill's own directory - tree. - -## What a mismatch means - -To classify a skill's state, compare three values: the lockfile's stored -`hash`, `hash-dir.sh` on the project's current copy -(`.claude/skills/`), and `hash-dir.sh` on the library's current -source (`~/.claude/skills/library/`). - -| stored vs. project copy | stored vs. library source | meaning | -|--------------------------|----------------------------|--------------------------------------| -| match | match | nothing to do | -| match | differs | library moved on — safe to update | -| differs | match | project customized on purpose — leave it | -| differs | differs | conflict — report, don't touch | - -## Writing to the lockfile - -- Adding a skill: append a new `{name, hash}` entry. -- Applying a safe update: overwrite that entry's `hash` in place with the - library's current hash. -- Removing a skill: delete its entry entirely. - -Never reorder or restructure existing entries beyond what an add, update, -or remove requires — this file is meant to diff cleanly in a project's -git history. diff --git a/.claude/skills/setup-skills/SKILL.md b/.claude/skills/setup-skills/SKILL.md deleted file mode 100644 index 74d924e..0000000 --- a/.claude/skills/setup-skills/SKILL.md +++ /dev/null @@ -1,46 +0,0 @@ ---- -name: setup-skills -description: Add relevant skills from the shared skills library to the current project. -disable-model-invocation: true ---- - -Adds opt-in, project-specific skills from `~/.claude/skills/library/` into -the current project's `.claude/skills/`, tracked in -`.claude/skills-lock.yaml` (see [LOCKFILE.md](LOCKFILE.md) for its schema). -Only ever adds — checking already-installed skills for updates is -[`update-skills`](../update-skills/SKILL.md)'s job, not this one's. - -## Steps - -1. Read `.claude/skills-lock.yaml` in the current project, if it exists. - Note every skill name already listed — these are already installed and - must not be re-proposed. - -2. List every skill under `~/.claude/skills/library/*/SKILL.md` and read - each one's `name` and `description`. - -3. Inspect the current project (file tree, manifests like - `pyproject.toml`/`package.json`, file extensions present, etc.) and - judge which library skills — excluding ones already installed — seem - relevant, the same way you'd reason about any unfamiliar codebase. - Propose that shortlist to the user with your reasoning, one line per - skill. If the user asks to see the full catalog instead, list every - library skill (minus already-installed ones) with its description. - -4. Let the user confirm, adjust, or pick freely from the full list. - -5. For each confirmed skill: - - If `.claude/skills//` already exists in the project and is - *not* in the lockfile, skip it and tell the user why (a same-named - skill already lives there and isn't tracked — remove or rename it - first if they want the library version). - - Otherwise, copy `~/.claude/skills/library//` to - `.claude/skills//` in the project, run - `~/.claude/skills/setup-skills/hash-dir.sh .claude/skills/`, - and append `{name, hash: }` to `.claude/skills-lock.yaml` - (create the file, an empty YAML list, if it doesn't exist yet). - -6. Report what was added and what was skipped, and why. - -Done when every confirmed skill is either copied and recorded in the -lockfile, or explicitly skipped with a stated reason. diff --git a/.claude/skills/setup-skills/hash-dir.sh b/.claude/skills/setup-skills/hash-dir.sh deleted file mode 100755 index 267fee7..0000000 --- a/.claude/skills/setup-skills/hash-dir.sh +++ /dev/null @@ -1,23 +0,0 @@ -#!/usr/bin/env bash -# Deterministic recursive hash of a directory's file contents. -# -# Hashes relative paths, not absolute ones, so two directories with -# identical contents hash identically regardless of where they live on -# disk (needed to compare a project's copied skill against the library -# source it was copied from). -# -# Usage: hash-dir.sh -set -euo pipefail - -if [ $# -ne 1 ]; then - echo "Usage: hash-dir.sh " >&2 - exit 1 -fi - -dir="$1" -if [ ! -d "$dir" ]; then - echo "Not a directory: $dir" >&2 - exit 1 -fi - -(cd "$dir" && find . -type f -print0 | sort -z | xargs -0 -r sha256sum) | sha256sum | awk '{print $1}' diff --git a/.claude/skills/test-driven-development/SKILL.md b/.claude/skills/test-driven-development/SKILL.md deleted file mode 100644 index 8c7db92..0000000 --- a/.claude/skills/test-driven-development/SKILL.md +++ /dev/null @@ -1,153 +0,0 @@ ---- -name: test-driven-development -description: Test-driven development. Use when the user wants to build features or fix bugs test-first, mentions "red-green-refactor", or wants integration tests. ---- - -# Test-Driven Development - -## Philosophy - -**Core principle**: Tests should verify behavior through public interfaces, not implementation details. Code can change entirely; tests shouldn't. - -**Good tests** are integration-style: they exercise real code paths through public APIs. They describe _what_ the system does, not _how_ it does it. A good test reads like a specification - "user can checkout with valid cart" tells you exactly what capability exists. These tests survive refactors because they don't care about internal structure. - -**Bad tests** are coupled to implementation. They mock internal collaborators, test private methods, or verify through external means (like querying a database directly instead of using the interface). The warning sign: your test breaks when you refactor, but behavior hasn't changed. If you rename an internal function and tests fail, those tests were testing implementation, not behavior. - -**Tautological tests** restate the implementation inside the assertion, so they pass by construction and give zero confidence. When the expected value is computed the way the code computes it — `expect(add(a, b)).toBe(a + b)`, snapshotting a figure you derived by hand the same way the code does, asserting a constant equals itself — the test can never disagree with the code: break the code wrong and the assertion breaks wrong with it. The expected value must come from an independent source of truth — a known-good literal, a worked example, the spec. - -See [tests.md](tests.md) for examples and [mocking.md](mocking.md) for mocking guidelines. - -## Anti-Pattern: Horizontal Slices - -**DO NOT write all tests first, then all implementation.** This is "horizontal slicing" - treating RED as "write all tests" and GREEN as "write all code." - -This produces **crap tests**: - -- Tests written in bulk test _imagined_ behavior, not _actual_ behavior -- You end up testing the _shape_ of things (data structures, function signatures) rather than user-facing behavior -- Tests become insensitive to real changes - they pass when behavior breaks, fail when behavior is fine -- You outrun your headlights, committing to test structure before understanding the implementation - -**Correct approach**: Vertical slices via tracer bullets. One test → one implementation → repeat. Each test responds to what you learned from the previous cycle. - -The test-writer sub-agent (below) is handed **one behavior at a time** and never sees the behavior backlog, so it can't bulk-write the suite. - -``` -WRONG (horizontal): - RED: test1, test2, test3, test4, test5 - GREEN: impl1, impl2, impl3, impl4, impl5 - -RIGHT (vertical): - RED→GREEN: test1→impl1 - RED→GREEN: test2→impl2 - RED→GREEN: test3→impl3 - ... -``` - -## Roles - -Every test is written by a **test-writer sub-agent**. The main agent writes every line of implementation, and never writes or edits a test. - -The sub-agent must not read the implementation source of the module under test — that is what keeps its tests from asserting _how_ instead of _what_. It works from the public interface alone. - -Use one `general-purpose` sub-agent for the whole task: spawn it at the first RED, then continue it with `SendMessage` for each subsequent RED, so it keeps the test file and conventions it established. Cold-spawn a replacement only if its ID is lost. - -### Test-writer sub-agent prompt — include: - -- **One behavior**, quoted verbatim from the acceptance criterion or the agreed behavior list. Never the task file, never the rest of the list. -- The **public interface** under test — signatures only. -- The existing test file(s) for the module, and the project's test conventions (fixtures, helpers, runner invocation). -- [tests.md](tests.md) and [mocking.md](mocking.md). -- The **independent source of truth for the expected value** — the spec excerpt, worked example, or known-good literal. Without it the sub-agent recomputes the expected value the way the code would, and the test is tautological. -- `.claude/CONTEXT.md` (if it exists) and any ADRs in the area, so test names and interface vocabulary match the project's domain language. -- The test-side checklist from [Checklist Per Cycle](#checklist-per-cycle), pasted in full — the sub-agent has no other access to it. -- The brief: "Write ONE test for this behavior. Do not read the implementation source of the module under test. Write it to the test file, run it, and confirm it fails with a genuine assertion failure — not an import, syntax, or collection error, which prove nothing. Report the test's name and the exact failure message you saw." - -## Workflow - -### 1. Planning - -When exploring the codebase, read `.claude/CONTEXT.md` (if it exists) so that test names and interface vocabulary match the project's domain language, and respect ADRs in the area you're touching. - -Identify opportunities for deep modules (small interface, deep implementation) — run the `/codebase-design` skill for the vocabulary and the testability checks. Do this regardless of what triggered this workflow. - -**If a task file is already in context** (e.g. passed to `/implement`, which called this skill), its acceptance criteria are the behavior list to test — the interface and priorities were already agreed during `/to-spec` and `/to-tasks`. Don't re-confirm them with the user; go straight to the tracer bullet. - -**Otherwise**, before writing any code: - -- [ ] Confirm with user what interface changes are needed -- [ ] Confirm with user which behaviors to test (prioritize) -- [ ] List the behaviors to test (not implementation steps) -- [ ] Get user approval on the plan - -Ask: "What should the public interface look like? Which behaviors are most important to test?" - -**You can't test everything.** Confirm with the user exactly which behaviors matter most. Focus testing effort on critical paths and complex logic, not every possible edge case. - -Planning stays with the main agent on both paths — exploration, interface, and the order behaviors are tested in. The sub-agent receives behaviors one at a time; it never chooses what to test next. - -### 2. Tracer Bullet - -ONE test that confirms ONE thing about the system: - -``` -RED: Spawn the test-writer sub-agent with the first behavior → it writes the test, runs it, reports a genuine failure -GREEN: Main agent writes minimal code to pass → test passes -``` - -This is your tracer bullet - proves the path works end-to-end. - -### 3. Incremental Loop - -For each remaining behavior: - -``` -RED: SendMessage the same sub-agent the next behavior → it writes the test, runs it, reports a genuine failure -GREEN: Main agent writes minimal code to pass → passes -``` - -Rules: - -- One test at a time -- Only enough code to pass current test -- Don't anticipate future tests -- Keep tests focused on observable behavior - -### When a test looks wrong - -The main agent never edits a sub-agent-authored test — not to fix an import, not to "simplify" an assertion, not to reach GREEN. - -- **Mechanical defect** — bad import path, a fixture or helper that doesn't exist, doesn't parse. Send the error output back to the sub-agent and let it fix its own test. -- **Semantic disagreement** — you believe the expected value or the asserted behavior is wrong. Stop and ask the user. Do not resolve it yourself; this disagreement is the signal the sub-agent exists to surface, and half the time it's the code that's wrong. - -### 4. Refactor - -After all tests pass, look for [refactor candidates](refactoring.md): - -- [ ] Extract duplication -- [ ] Deepen modules (move complexity behind simple interfaces) -- [ ] Apply SOLID principles where natural -- [ ] Consider what new code reveals about existing code -- [ ] Run tests after each refactor step - -**Never refactor while RED.** Get to GREEN first. - -A test that breaks during refactor means the refactor broke behavior — fix the code. The one exception is a public interface change you made deliberately (a module deepened, a signature moved, as agreed in the plan): send the interface change to the sub-agent and let it update its own tests. There is no case where the main agent edits the test itself. - -## Checklist Per Cycle - -Test-writer sub-agent, per test — paste into its prompt: - -``` -[ ] Test describes behavior, not implementation -[ ] Test uses public interface only -[ ] Test would survive internal refactor -[ ] Expected values are independent literals, not recomputed from the code -``` - -Main agent, per GREEN: - -``` -[ ] Code is minimal for this test -[ ] No speculative features added -``` diff --git a/.claude/skills/test-driven-development/mocking.md b/.claude/skills/test-driven-development/mocking.md deleted file mode 100644 index 71cbfee..0000000 --- a/.claude/skills/test-driven-development/mocking.md +++ /dev/null @@ -1,59 +0,0 @@ -# When to Mock - -Mock at **system boundaries** only: - -- External APIs (payment, email, etc.) -- Databases (sometimes - prefer test DB) -- Time/randomness -- File system (sometimes) - -Don't mock: - -- Your own classes/modules -- Internal collaborators -- Anything you control - -## Designing for Mockability - -At system boundaries, design interfaces that are easy to mock: - -**1. Use dependency injection** - -Pass external dependencies in rather than creating them internally: - -```typescript -// Easy to mock -function processPayment(order, paymentClient) { - return paymentClient.charge(order.total); -} - -// Hard to mock -function processPayment(order) { - const client = new StripeClient(process.env.STRIPE_KEY); - return client.charge(order.total); -} -``` - -**2. Prefer SDK-style interfaces over generic fetchers** - -Create specific functions for each external operation instead of one generic function with conditional logic: - -```typescript -// GOOD: Each function is independently mockable -const api = { - getUser: (id) => fetch(`/users/${id}`), - getOrders: (userId) => fetch(`/users/${userId}/orders`), - createOrder: (data) => fetch('/orders', { method: 'POST', body: data }), -}; - -// BAD: Mocking requires conditional logic inside the mock -const api = { - fetch: (endpoint, options) => fetch(endpoint, options), -}; -``` - -The SDK approach means: -- Each mock returns one specific shape -- No conditional logic in test setup -- Easier to see which endpoints a test exercises -- Type safety per endpoint diff --git a/.claude/skills/test-driven-development/refactoring.md b/.claude/skills/test-driven-development/refactoring.md deleted file mode 100644 index 2b3cd01..0000000 --- a/.claude/skills/test-driven-development/refactoring.md +++ /dev/null @@ -1,10 +0,0 @@ -# Refactor Candidates - -After TDD cycle, look for: - -- **Duplication** → Extract function/class -- **Long methods** → Break into private helpers (keep tests on public interface) -- **Shallow modules** → Combine or deepen — see DEEPENING.md in `/codebase-design` for dependency categories and seam discipline -- **Feature envy** → Move logic to where data lives -- **Primitive obsession** → Introduce value objects -- **Existing code** the new code reveals as problematic diff --git a/.claude/skills/test-driven-development/tests.md b/.claude/skills/test-driven-development/tests.md deleted file mode 100644 index 7ab8647..0000000 --- a/.claude/skills/test-driven-development/tests.md +++ /dev/null @@ -1,77 +0,0 @@ -# Good and Bad Tests - -## Good Tests - -**Integration-style**: Test through real interfaces, not mocks of internal parts. - -```typescript -// GOOD: Tests observable behavior -test("user can checkout with valid cart", async () => { - const cart = createCart(); - cart.add(product); - const result = await checkout(cart, paymentMethod); - expect(result.status).toBe("confirmed"); -}); -``` - -Characteristics: - -- Tests behavior users/callers care about -- Uses public API only -- Survives internal refactors -- Describes WHAT, not HOW -- One logical assertion per test - -## Bad Tests - -**Implementation-detail tests**: Coupled to internal structure. - -```typescript -// BAD: Tests implementation details -test("checkout calls paymentService.process", async () => { - const mockPayment = jest.mock(paymentService); - await checkout(cart, payment); - expect(mockPayment.process).toHaveBeenCalledWith(cart.total); -}); -``` - -Red flags: - -- Mocking internal collaborators -- Testing private methods -- Asserting on call counts/order -- Test breaks when refactoring without behavior change -- Test name describes HOW not WHAT -- Verifying through external means instead of interface - -```typescript -// BAD: Bypasses interface to verify -test("createUser saves to database", async () => { - await createUser({ name: "Alice" }); - const row = await db.query("SELECT * FROM users WHERE name = ?", ["Alice"]); - expect(row).toBeDefined(); -}); - -// GOOD: Verifies through interface -test("createUser makes user retrievable", async () => { - const user = await createUser({ name: "Alice" }); - const retrieved = await getUser(user.id); - expect(retrieved.name).toBe("Alice"); -}); -``` - -**Tautological tests**: Expected value restates the implementation, so the test passes by construction. - -```typescript -// BAD: Expected value is recomputed the way the code computes it -test("calculateTotal sums line items", () => { - const items = [{ price: 10 }, { price: 5 }]; - const expected = items.reduce((sum, i) => sum + i.price, 0); - expect(calculateTotal(items)).toBe(expected); -}); - -// GOOD: Expected value is an independent, known literal -test("calculateTotal sums line items", () => { - expect(calculateTotal([{ price: 10 }, { price: 5 }])).toBe(15); -}); -``` diff --git a/.claude/skills/to-spec/SKILL.md b/.claude/skills/to-spec/SKILL.md deleted file mode 100644 index 489c610..0000000 --- a/.claude/skills/to-spec/SKILL.md +++ /dev/null @@ -1,21 +0,0 @@ ---- -name: to-spec -description: Turn the current conversation into a spec and write it to .claude/spec/ — no interview, just synthesis of what you've already discussed. -disable-model-invocation: true ---- - -This skill takes the current conversation context and codebase understanding and produces a spec. Do NOT interview the user — just synthesize what you already know. - -If the conversation doesn't actually contain a feature or problem to synthesize a spec from, say so and ask what it's for instead of fabricating one. - -## Process - -1. Explore the repo until you can name the existing modules, flows, and seams the feature will touch, if you haven't already. Use the project's domain glossary vocabulary throughout the spec, and respect any ADRs in the area you're touching. - -2. Sketch out the seams at which you're going to test the feature. Existing seams should be preferred to new ones. Use the highest seam possible. If new seams are needed, propose them at the highest point you can. The fewer seams across the codebase, the better - the ideal number is one. - -Check with the user that these seams match their expectations. - -3. Derive a short kebab-case feature-slug from the feature's name (e.g. `checkout-flow`). Tell the user the path you're about to write to (`.claude/spec/.md`). If a file already exists there, summarize what would change and confirm with the user before overwriting it — never overwrite silently. - -4. Write the spec using the format in [SPEC-FORMAT.md](./SPEC-FORMAT.md) to `.claude/spec/.md`, creating the `.claude/spec/` directory if it doesn't exist yet. diff --git a/.claude/skills/to-spec/SPEC-FORMAT.md b/.claude/skills/to-spec/SPEC-FORMAT.md deleted file mode 100644 index 15e82f8..0000000 --- a/.claude/skills/to-spec/SPEC-FORMAT.md +++ /dev/null @@ -1,55 +0,0 @@ -# Spec Format - -## Template - -```md -## Problem Statement - -The problem that the user is facing, from the user's perspective. - -## Solution - -The solution to the problem, from the user's perspective. - -## User Stories - -An extensive, numbered list of user stories, covering all aspects of the feature. Each user story should be in the format of: - -1. As an , I want a , so that - - -1. As a mobile bank customer, I want to see balance on my accounts, so that I can make better informed decisions about my spending - - -## Implementation Decisions - -A list of implementation decisions that were made. This can include: - -- The modules that will be built/modified -- The interfaces of those modules that will be modified -- Technical clarifications from the developer -- Architectural decisions -- Schema changes -- API contracts -- Specific interactions - -## Testing Decisions - -A list of testing decisions that were made. Include: - -- A description of what makes a good test (only test external behavior, not implementation details) -- Which modules will be tested -- Prior art for the tests (i.e. similar types of tests in the codebase) - -## Out of Scope - -A description of the things that are out of scope for this spec. - -## Further Notes - -Any further notes about the feature. -``` - -## Rules - -- **Don't include specific file paths or code snippets.** They may end up being outdated very quickly. Exception: if a prototype produced a snippet that encodes a decision more precisely than prose can (state machine, reducer, schema, type shape), inline it within the relevant decision in Implementation Decisions and note briefly that it came from a prototype. Trim to the decision-rich parts — not a working demo, just the important bits. diff --git a/.claude/skills/to-tasks/SKILL.md b/.claude/skills/to-tasks/SKILL.md deleted file mode 100644 index 112d382..0000000 --- a/.claude/skills/to-tasks/SKILL.md +++ /dev/null @@ -1,57 +0,0 @@ ---- -name: to-tasks -description: Break a plan or spec into independently-grabbable task files under .claude/tasks/ using tracer-bullet vertical slices. -disable-model-invocation: true ---- - -# To Tasks - -## Process - -### 1. Gather context - -Work from whatever is already in the conversation context. If the user passes a spec path or other reference as an argument, read it directly. - -Determine the feature-slug this breakdown belongs to, if any: if a spec file is in context or was passed as an argument, derive it from the filename (`.claude/spec/.md` → ``) for each task's `spec` field — see [TASK-FORMAT.md](./TASK-FORMAT.md) for the field's rules. If no spec file exists, proceed without one. - -### 2. Explore the codebase (optional) - -If you have not already explored the codebase, do so to understand the current state of the code. Task titles and descriptions should use the project's domain glossary vocabulary, and respect ADRs in the area you're touching. - -Look for opportunities to prefactor the code to make the implementation easier. "Make the change easy, then make the easy change." - -### 3. Draft vertical slices - -Break the plan into **tracer bullet** tasks — vertical slices, not horizontal layers. - - - -- Each slice delivers a narrow but COMPLETE path through every layer the change requires (schema, API, UI, tests), never a horizontal slice of just one -- A completed slice is demoable or verifiable on its own -- Any prefactoring should be done first - - - -### 4. Quiz the user - -Number slices with a single sequence shared across every file already in `.claude/tasks/`: scan for the highest existing `NNNN` (four-digit, zero-padded decimal, `0000`-`9999`) and increment from there. Never restart the sequence per feature and never reuse a number. - -Present the proposed breakdown as a numbered list. For each slice, show: - -- **File**: the `NNNN-slice-slug` it will be written as, per the numbering above -- **Blocked by**: which other slices (if any) must complete first — "None" if it can start immediately -- **User stories covered**: which user stories this addresses (if the source material has them) - -Ask the user: - -- Does the granularity feel right? (too coarse / too fine) -- Are the dependency relationships correct? -- Should any slices be merged or split further? - -Iterate until the user approves the breakdown, including the proposed numbers and slugs. - -### 5. Write the task files - -For each approved slice, write a file to `.claude/tasks/-.md` (create the directory if it doesn't exist) using the numbers and slugs approved in step 4. Use the template in [TASK-FORMAT.md](./TASK-FORMAT.md). - -Do NOT modify the parent spec file (`.claude/spec/.md`) when writing tasks. diff --git a/.claude/skills/to-tasks/TASK-FORMAT.md b/.claude/skills/to-tasks/TASK-FORMAT.md deleted file mode 100644 index 1ac9871..0000000 --- a/.claude/skills/to-tasks/TASK-FORMAT.md +++ /dev/null @@ -1,28 +0,0 @@ -# Task Format - -## Template - -```md ---- -spec: -blocked-by: ---- - -## What to build - -A concise description of this vertical slice. Describe the end-to-end behavior, not layer-by-layer implementation. - -## Acceptance criteria - -- [ ] Criterion 1 -- [ ] Criterion 2 -- [ ] Criterion 3 -``` - -## Rules - -- **`spec`**: the feature-slug this task was written from. Omit the field entirely if there's no spec. -- **`blocked-by`**: which other task(s) must complete before this one can start. Omit the field entirely if there are none. Each value is the blocking task's full `-` filename stem, not just its slug. A single blocker is a bare string (`blocked-by: 0010-add-schema`); more than one is a YAML list (`blocked-by: [0010-add-schema, 0011-wire-api]`). Once written, keep the field even after the referenced task is completed — it's a permanent record of the dependency, not a "still blocked" flag. -- **Don't include specific file paths or code snippets** in "What to build" — they go stale fast. Exception: if a prototype produced a snippet that encodes a decision more precisely than prose can (state machine, reducer, schema, type shape), inline it here and note briefly that it came from a prototype. Trim to the decision-rich parts — not a working demo, just the important bits. -- A task is done when every criterion in "Acceptance criteria" is resolved: mark `[x]` as satisfied, or `[-]` if deliberately dropped (`/implement` records the reason in the task's Implementation Notes) — track completion here, not anywhere else. -- A slice becomes pickable once every task named in `blocked-by` is done (all of its acceptance criteria resolved) — check the referenced tasks' state, not just whether the field is present. The file's number is an identifier and a rough ordering hint, not a strict gate — sibling slices with no blockers can be worked in parallel. diff --git a/.claude/skills/update-skills/SKILL.md b/.claude/skills/update-skills/SKILL.md deleted file mode 100644 index 2d11a89..0000000 --- a/.claude/skills/update-skills/SKILL.md +++ /dev/null @@ -1,52 +0,0 @@ ---- -name: update-skills -description: Check the current project's installed library skills for upstream changes and apply the safe ones. -disable-model-invocation: true ---- - -Compares every skill listed in the current project's -`.claude/skills-lock.yaml` (see [LOCKFILE.md](../setup-skills/LOCKFILE.md) -for its schema) against both the project's own copy and the current -library source, and decides what to do about each one. Never installs a -skill that isn't already there — that's -[`setup-skills`](../setup-skills/SKILL.md)'s job. - -## Steps - -1. Read `.claude/skills-lock.yaml`. If it doesn't exist or is empty, tell - the user there's nothing to check and stop. - -2. For each `{name, hash}` entry, compute: - - `project_hash`: `~/.claude/skills/setup-skills/hash-dir.sh .claude/skills/` - - `library_hash`: `~/.claude/skills/setup-skills/hash-dir.sh ~/.claude/skills/library/` - - If either path is missing entirely, report that anomaly for this skill - (don't try to classify it) and move on to the next entry. - -3. Classify each entry against the table in - [LOCKFILE.md](../setup-skills/LOCKFILE.md#what-a-mismatch-means), - using `project_hash` in place of "project copy" and `library_hash` in - place of "library source". The two outcomes that need action below are - **safe update** (stored matches project, differs from library) and - **conflict** (stored differs from both). "Locally customized" needs no - message beyond the summary. - -4. If there are any safe updates, list them by name and ask for one - confirmation to apply all of them — unless the user's invocation - already included an explicit go-ahead argument (e.g. `-y`, `yes`), in - which case apply them without asking. Applying means: delete - `.claude/skills//` entirely and copy - `~/.claude/skills/library//` in its place, so no file the project - copy had but the library no longer has can survive — then recompute its - hash and overwrite that entry's `hash` in `.claude/skills-lock.yaml` in - place. - -5. For every conflict, report it and show a recursive diff between the - project's copy and the library's current version - (`diff -ru .claude/skills/ ~/.claude/skills/library/`). - Do not modify the project's copy or the lockfile entry for a - conflicted skill under any circumstances — surfacing it is the whole - job here. - -6. Finish with a summary: updated, left alone (customized), conflicted, - already current, and any anomalies from step 2. diff --git a/.config/alacritty/alacritty.toml b/.config/alacritty/alacritty.toml deleted file mode 100644 index 7411716..0000000 --- a/.config/alacritty/alacritty.toml +++ /dev/null @@ -1,88 +0,0 @@ -[general] -working_directory = "None" -live_config_reload = true - -[env] -TERM = "xterm-256color" -WINIT_X11_SCALE_FACTOR = "1.0" - -[window] -dimensions = { columns = 100, lines = 30 } -dynamic_padding = true -decorations = "Full" -opacity = 0.8 -title = "Alacritty@CachyOS" -class = { instance = "Alacritty", general = "Alacritty" } -decorations_theme_variant = "Dark" - -[scrolling] -history = 10000 -multiplier = 3 - -[font] -normal = { family = "MesloLGS Nerd Font Mono", style = "Regular" } -bold = { family = "MesloLGS Nerd Font Mono", style = "Bold" } -italic = { family = "MesloLGS Nerd Font Mono", style = "Italic" } -bold_italic = { family = "MesloLGS Nerd Font Mono", style = "Bold Italic" } -size = 12.0 - -[colors] -draw_bold_text_with_bright_colors = true - -[colors.primary] -background = "0x2E3440" -foreground = "0xD8DEE9" - -[colors.normal] -black = "0x3B4252" -red = "0xBF616A" -green = "0xA3BE8C" -yellow = "0xEBCB8B" -blue = "0x81A1C1" -magenta = "0xB48EAD" -cyan = "0x88C0D0" -white = "0xE5E9F0" - -[colors.bright] -black = "0x4C566A" -red = "0xBF616A" -green = "0xA3BE8C" -yellow = "0xEBCB8B" -blue = "0x81A1C1" -magenta = "0xB48EAD" -cyan = "0x8FBCBB" -white = "0xECEFF4" - -[selection] -semantic_escape_chars = ",│`|:\"' ()[]{}<>\t" -save_to_clipboard = true - -[cursor] -style = { shape = "Underline", blinking = "Off" } -unfocused_hollow = true -thickness = 0.15 - -[mouse] -hide_when_typing = true -bindings = [ -{ mouse = "Middle", mods = "None", action = "PasteSelection" }, -] - -[keyboard] -bindings = [ -{ key = "Paste", mods = "None", action = "Paste" }, -{ key = "Copy", mods = "None", action = "Copy" }, -{ key = "L", mods = "Control", action = "ClearLogNotice" }, -{ key = "L", mods = "Control", mode = "~Vi", chars = "\f" }, -{ key = "PageUp", mods = "Shift", mode = "~Alt", action = "ScrollPageUp" }, -{ key = "PageDown", mods = "Shift", mode = "~Alt", action = "ScrollPageDown" }, -{ key = "Home", mods = "Shift", mode = "~Alt", action = "ScrollToTop" }, -{ key = "End", mods = "Shift", mode = "~Alt", action = "ScrollToBottom" }, -{ key = "V", mods = "Control|Shift", action = "Paste" }, -{ key = "C", mods = "Control|Shift", action = "Copy" }, -{ key = "F", mods = "Control|Shift", action = "SearchForward" }, -{ key = "B", mods = "Control|Shift", action = "SearchBackward" }, -{ key = "C", mods = "Control|Shift", mode = "Vi", action = "ClearSelection" }, -{ key = "Key0", mods = "Control", action = "ResetFontSize" }, -] - diff --git a/.config/dot/.claude/scheduled_tasks.lock b/.config/dot/.claude/scheduled_tasks.lock deleted file mode 100644 index 0a93074..0000000 --- a/.config/dot/.claude/scheduled_tasks.lock +++ /dev/null @@ -1 +0,0 @@ -{"sessionId":"d1732179-27b7-49fb-82af-6669b783a444","pid":45537,"procStart":"7266197","acquiredAt":1783288905666} \ No newline at end of file diff --git a/.config/dot/.claude/spec/dot-kde.md b/.config/dot/.claude/spec/dot-kde.md deleted file mode 100644 index 7f92452..0000000 --- a/.config/dot/.claude/spec/dot-kde.md +++ /dev/null @@ -1,76 +0,0 @@ -## Problem Statement - -Several KDE settings on this machine have already been changed by hand away from their KDE/CachyOS defaults — the caps-lock/Escape swap is live right now, and screenshot-related keybind changes (Spectacle bindings, moving Lock Session off `Meta+L`) are planned next — but none of this is tracked anywhere in the dotfiles repo. If the machine were rebuilt today, these settings would silently revert to defaults with no record of what needs to be reapplied. There's also no way to notice *unexpected* drift (a setting that changed without the owner deliberately choosing to change it), and no tooling to bring a manually-tweaked setting under tracking without hand-writing one-off `kwriteconfig6`/D-Bus calls — exactly the accumulation of ad hoc scripts the dotfiles project has otherwise avoided. - -## Solution - -Add a `dot kde` subcommand family with three verbs: - -- **`dot kde apply`** — pushes every setting declared in a tracked manifest onto the live KDE session (repo → system). -- **`dot kde diff`** — a broad, read-only scan reporting every live KDE setting that differs from its default, tagging each mismatch as either already-declared (in the manifest) or undeclared (system → discovery, no write). -- **`dot kde save`** — the write path into the manifest (system → repo). Run with no arguments, it refreshes every already-declared entry's stored value from the live system. Run with explicit coordinates, it begins tracking one new setting, seeded from its current live value. - -The manifest is a single flat, mechanism-agnostic file: opaque `identifier=value` lines. `dot kde` internally figures out *how* to read/write a given identifier (three different underlying mechanisms exist across KDE's config surface), so the manifest itself never needs to know or care how KDE happens to store that particular setting. - -## User Stories - -1. As the machine owner, I want to declare that a KDE setting should have a specific value, so that a freshly-built machine ends up with the same intentional deviations from KDE's defaults without me re-discovering and re-typing the underlying `kwriteconfig6`/D-Bus incantations. -2. As the machine owner, I want `dot kde apply` to push all declared settings onto a live session in one idempotent command, so that re-running it after a KDE update or on a new machine is safe and has no unintended side effects. -3. As the machine owner, I want `dot kde diff` to show me every KDE setting currently different from default, so that I can catch drift I didn't intend, not just check the handful of settings I already know about. -4. As the machine owner, I want `dot kde diff`'s output to distinguish "this is already declared and intentional" from "this is undeclared and I've never seen it before," so that the noise of broad scanning doesn't bury genuinely unexpected changes. -5. As the machine owner, I want to run `dot kde save` with no arguments and have every already-tracked setting's manifest value refreshed from whatever is currently live, so that if I tweak a tracked setting by hand (e.g. change a keybind in System Settings) the manifest catches up without me re-typing its identifier. -6. As the machine owner, I want to run `dot kde save` with an explicit identifier to begin tracking one specific setting I just noticed via `diff`, so that I control exactly what enters the manifest instead of everything non-default being swept in at once. -7. As the machine owner, I want global keyboard shortcuts to be read and written through KDE's own shortcut-management service rather than by hand-editing `kglobalshortcutsrc`, so that changes take effect immediately in the running session and I never have to reconstruct KDE's internal triplet bookkeeping (current/default/friendly-name) myself. -8. As the machine owner, I want KConfigXT-schema-backed settings to have their "default" value discovered automatically wherever KDE's schema declares it, so that broad drift-scanning covers as much of the KDE config surface as possible without me manually cataloguing every setting I might ever care about. -9. As a future contributor to this dotfiles repo, I want `dot kde`'s subcommand files to live alongside its Python helper in one place, discoverable the same way every other `dot` subcommand is, so that adding this feature doesn't require bespoke wiring outside the established convention. - -## Implementation Decisions - -- **Subcommand family**: `dot kde apply` / `dot kde diff` / `dot kde save`, following the project's existing nested-subcommand dispatch convention (each level checks for `help` as its first positional argument before `argparse`, calling its own usage function). -- **`dot kde save` has two modes**: - - No arguments: iterate every identifier already in the manifest, read its current live value via the appropriate mechanism, and rewrite the manifest with the refreshed value. - - Explicit coordinates given: read the current live value for that one setting and add it to the manifest as a new declared entry. This is the only way new entries enter the manifest — there is no bulk/"track everything currently non-default" mode, by design, so that curation stays deliberate. -- **`dot kde diff`**: enumerates every setting it knows how to check (see mechanisms below), compares live vs. default, and reports every mismatch. Each reported mismatch is tagged as declared (present in the manifest, i.e. an intentional, already-tracked deviation) or undeclared (never explicitly declared). Diff never writes anything. -- **Manifest**: - - Location: a flat file directly under `~/.config/dot/` (not nested in a subdirectory — no near-term plan for multiple KDE-like targets that would justify one), named to convey "the set of KDE settings intentionally different from default." - - Format: plain text, one entry per line, `identifier=value`, split on the *first* `=` only (so values may themselves contain `=`). - - Identifier scheme: `file.group.key`, split on the first two `.`s only (so the key portion may contain further dots, spaces, or other characters freely — relevant for `kglobalshortcutsrc` action names, which can contain spaces). - - The manifest carries no mechanism/type discriminator field. It is a pure `identifier → value` map; `dot kde` decides internally how to resolve a given identifier. -- **Three underlying mechanisms**, dispatched purely by inspecting the identifier (no stored metadata): - 1. **Shortcuts** (`kglobalshortcutsrc..`) — resolved not by editing the rc file directly, but through KDE's `kglobalaccel` D-Bus service: - - Read current value: `shortcut(actionId)`. - - Read default value: `defaultShortcut(actionId)`. - - Write: `setShortcut(actionId, keys, flags)` with `flags = NoAutoloading` (so the declared value always wins over any previously-saved shortcut; using the `Autoloading` flag would make `apply` a no-op after the first run). - - `actionId` is a 4-element list: `[componentUniqueName, actionUniqueName, componentFriendlyName, actionFriendlyName]` (confirmed against KDE's own `actionIdFields` enum and verified live via `gdbus`). Only `componentUnique`/`actionUnique` are stored in the manifest; the two friendly-name fields (needed to actually place the D-Bus call) are resolved dynamically at call time by looking up the component's shortcut list, not stored. - - No read-modify-write is needed for this mechanism — `setShortcut` only ever touches the live/current value, never the default, so there's no risk of clobbering KDE's own bookkeeping. - 2. **KConfigXT schema-backed settings** (most `kwinrc`, `kdeglobals`, etc. entries) — read/write via `kreadconfig6`/`kwriteconfig6`; the "default" value comes from the setting's `.kcfg` schema. - - The `(rcfile → [kcfg files])` mapping table is auto-derived at runtime by scanning the system's `.kcfg` schema directory for files that statically declare their target rc file (``), plus a small hand-maintained list for the exceptions that declare `` (i.e. the target file is only known at runtime by the owning app, not in the schema — `kwin.kcfg` is a known example). - - This mechanism is what enables `diff`'s broad-scan coverage: every entry reachable through the mapping table can be checked automatically, not just entries someone has already thought to add to the manifest. - 3. **Freeform/schema-less settings** (e.g. `kxkbrc`'s `Options=` line) — read/write via `kreadconfig6`/`kwriteconfig6`; there is no schema, so "default" is defined as "the key is absent." Because there's no schema to enumerate from, this mechanism cannot participate in broad undeclared-drift discovery the way schema-backed settings can — it can only be checked for settings that are already declared in the manifest. - - Mechanism selection for a given identifier: if the rc file is `kglobalshortcutsrc`, use the shortcuts mechanism; otherwise, if the mapping table resolves the `(rcfile, group, key)` to a schema, use the schema-backed mechanism; otherwise, treat it as freeform. -- **File/module layout**: the fish dispatcher and its Python helper live together in one subdirectory under the project's existing commands location, rather than the Python helper sitting as a same-directory sibling of a same-named fish file at the top level. -- **Cross-cutting change to `dot` itself**: the subcommand-discovery mechanism (used both for help-listing and for dispatch) is extended to glob one additional directory level deep, not just the flat top level — required to support the subcommand-plus-helper layout above. This must be updated in both places the discovery logic currently exists (they are intentionally duplicated today rather than shared, for fish-autoload reasons), and applies to any future subcommand that wants a companion file, not just this one. - -## Testing Decisions - -- **Guiding principle**: tests should exercise this feature's own logic (manifest parsing, identifier dispatch, mapping-table auto-derivation, mechanism selection), not re-verify that external dependencies (`kreadconfig6`, `kwriteconfig6`, the KDE session itself) work correctly. -- **Primary seam**: full CLI invocation of `dot kde apply` / `dot kde diff` / `dot kde save`, run against a scratch `$HOME`, mirroring the existing project convention for testing `dot` subcommands (override `$HOME` per test case, no mocking of the real `kreadconfig6`/`kwriteconfig6` binaries — they run for real against fixture rc files under the scratch home). This covers the schema-backed and freeform mechanisms end-to-end: manifest read/write, identifier parsing, mechanism dispatch, and mapping-table-driven default lookup. -- **New seam introduced for this feature**: the KConfigXT schema directory is normally a fixed system path outside `$HOME`. To make the auto-derivation logic testable without depending on (or mutating) the real system's schema files, the schema directory location must be overridable (e.g. via an environment variable), defaulting to the real system path in normal use and pointing at a small fixture directory of synthetic `.kcfg` files in tests. -- **Deliberately not covered by automated tests**: the shortcuts mechanism (`kglobalaccel` D-Bus calls). It depends on a live, already-running session service that isn't practically substitutable without building dedicated mock infrastructure, which is disproportionate to what it would protect (three D-Bus calls). This path is verified manually against the real session instead. -- **Prior art**: the existing test suite for `dot`'s other subcommands already establishes the scratch-`$HOME`-plus-`fishtape` pattern this feature reuses. - -## Out of Scope - -- A `dot setup`-style subcommand for machine bootstrap tasks (extra groups, etc.) — considered during planning and set aside as not currently relevant. -- Folder naming / XDG user-dirs conventions — a real, separate piece of planned work, but standalone from `dot kde` and not part of this spec. -- Tracking Plasma's panel layout (`plasma-org.kde.plasma.desktop-appletsrc`) — previously decided this doesn't need tracking, since the current panel is CachyOS's own shipped default and reproduces automatically on a fresh install. -- An "empirical fallback" mechanism (spinning up a scratch config environment to let an app generate its own default config for diffing) — not needed given the three mechanisms above cover everything currently in scope; noted only as a possible future extension if some setting fits none of them. -- A bulk/`--all` mode for `dot kde save` — deliberately excluded so that every new manifest entry is a deliberate choice. -- Interactive picker UX for `diff`/`save` (e.g. selecting an undeclared entry from a list rather than typing its identifier) — not part of this spec. -- `dot voice` (hands-free dictation) — an unrelated, separately shelved piece of work, not touched by this feature. - -## Further Notes - -- The caps-lock/Escape swap (`kxkbrc`'s `Options=caps:escape_shifted_capslock`) is already live on this machine by hand, unrecorded anywhere — it's a ready-made first real candidate for the explicit-coordinates form of `dot kde save` once built, and a natural first end-to-end smoke test beyond the automated suite. -- The screenshot-related keybind work (Spectacle bindings, moving Lock Session off `Meta+L` to `Meta+X`, renaming Spectacle's save folder) was the original motivating case for this feature but is applied *through* `dot kde apply`/`save` rather than being separate work — once `dot kde` exists, those keybind changes are just manifest entries. -- Per the project's own cross-cutting convention, once any keybind changes are actually applied via this feature, the corresponding rows in the project's keybindings reference document need to be added/updated in the same change. diff --git a/.config/dot/.claude/spec/dot-setup-folders.md b/.config/dot/.claude/spec/dot-setup-folders.md deleted file mode 100644 index 5c95ba5..0000000 --- a/.config/dot/.claude/spec/dot-setup-folders.md +++ /dev/null @@ -1,59 +0,0 @@ -## Problem Statement - -The old `~/wrk/dotfiles` repo's `setup_folders` (part of its bash `bin/dot init`) renamed the standard XDG user folders to short names (`Documents→doc`, `Downloads→dwn`, etc.) for better fish shell-completion ergonomics — shorter shared prefixes are easier to disambiguate by typing fewer characters. That behavior has no equivalent in the new bare-repo `dot` CLI. Right now this machine's `user-dirs.dirs` is untracked and has drifted from even the old convention: it uses the full XDG default names, plus an ad hoc `XDG_PROJECTS_DIR=$HOME/Projects` line that never existed in the old repo at all. If this machine were rebuilt today, none of the short-name convention would be restored, and the current drifted state isn't recorded anywhere. - -## Solution - -Add a `folders` task to a new `dot setup` subcommand family (the general home for idempotent, re-runnable machine-setup tasks, as opposed to `dot init`'s one-shot bootstrap). `dot setup folders` brings the 8 standard XDG user directories under the project's short-name convention, tracks the resulting `user-dirs.dirs` directly as a plain dotfile, and safely migrates any content sitting in the old, full-named folders into their short-named replacements. - -`~/wrk` (already in active use, e.g. `~/wrk/dotfiles`) replaces the old `Projects`-style folder as the general working-files location, but is treated as a plain convention-only directory, not a tracked XDG category. - -## User Stories - -1. As the machine owner, I want the standard XDG user folders renamed to short names (`doc`, `dwn`, `mus`, `pic`, `vid`, `.desktop`), so that fish-completion on my home directory has shorter, easier-to-disambiguate shared prefixes than the full XDG default names. -2. As the machine owner, I want `Templates` and `Public` (both unused) collapsed into a single hidden `.ignoreme` folder, so that apps respecting `XDG_TEMPLATES_DIR`/`XDG_PUBLICSHARE_DIR` don't scatter files directly into `$HOME`, without needing two separate unused folders. -3. As the machine owner, I want the nested `Pictures/Screenshots` folder lowercased to `pic/screenshots` in the same pass as the `Pictures→pic` rename, so that the screenshot folder matches the rest of the short-folder naming convention without a separate migration step. -4. As the machine owner, I want `~/wrk` to have no XDG variable pointing at it, so that a non-standard, barely-recognized XDG extension (`XDG_PROJECTS_DIR`) doesn't get tracked for a directory that already works fine as a plain convention. -5. As the machine owner, I want `user-dirs.dirs` tracked directly in the bare dotfiles repo like any other plain dotfile, so that the desired short names are recorded and restorable on a fresh machine without needing a code-generation step. -6. As the machine owner, I want `dot setup folders` to migrate content out of any legacy full-named folder into its short-named replacement automatically when the legacy folder is empty, so that re-running setup on a fresh install requires no manual folder shuffling. -7. As the machine owner, I want `dot setup folders` to stop and ask for explicit confirmation before moving anything out of a legacy folder that actually has content in it, so that I never silently lose files to an automated migration I forgot was going to run. -8. As the machine owner, I want confirmation to be satisfiable via a `--yes` flag rather than an interactive prompt, so that the same command works identically whether I'm running it by hand or from an automated/tested context. -9. As the machine owner, I want a filename collision between a legacy folder and an already-populated short-named target to never be silently overwritten, so that re-running the migration after a partial/interrupted prior run can't destroy a file just because both sides happen to have a same-named entry. -10. As the machine owner, I want to be told which files were skipped due to a collision and have the legacy folder left in place when that happens, so that I have a clear, actionable signal that something needs manual attention instead of silent partial data loss. -11. As the machine owner, I want `dot setup folders` to notify running apps of the directory changes via `xdg-user-dirs-update` after migrating, so that session-long apps pick up the new paths without requiring a full logout/login. -12. As the machine owner, I want to run `dot setup` with no arguments to perform every machine-setup task (folders plus future ones like extra groups) in one command, so that setting up a fresh machine doesn't require remembering and running each task individually. -13. As the machine owner, I want to also be able to run `dot setup folders` on its own, so that I can re-run just this one task in isolation (e.g. after a confirmation was declined) without re-running unrelated setup tasks. - -## Implementation Decisions - -- **Subcommand family**: `dot setup`, following the project's existing nested-subcommand dispatch convention (`help`-then-`argparse`, `_dot__usage`). Bare `dot setup` (no arguments) runs every machine-setup task unconditionally (folders, plus future tasks such as extra groups, mirroring the old bash `bin/dot init`'s dual-mode: no-args ran everything, an explicit keyword ran just one task). `dot setup folders` runs just the folders task. -- **Folder mapping** (identical to the old repo's `setup_folders`, no changes): `Desktop→.desktop`, `Documents→doc`, `Downloads→dwn`, `Music→mus`, `Pictures→pic`, `Videos→vid`, `Templates→.ignoreme`, `Public→.ignoreme`. `Templates` and `Public` both point at the *same* `.ignoreme` folder, as before. -- **Nested screenshots rename**: as part of the same `Pictures→pic` migration pass, the nested `Screenshots` folder (currently created empty by KDE/Spectacle defaults) is renamed to lowercase `screenshots`, so the result is `pic/screenshots`. This is folded into the folders task rather than deferred to the separate Spectacle-keybind work, since it's the same naming-convention concern and falls out for free once `Pictures/*` is moved into `pic/`. -- **`wrk` is out of the XDG mapping**: no `XDG_PROJECTS_DIR` (or any other XDG variable) is written for it. It's a plain, convention-only directory. The currently-existing ad hoc `~/Projects` folder (created by this machine's diverged, untracked `user-dirs.dirs`) is left alone — out of scope for the folders task, since it was never one of the 8 standard XDG categories the task manages, and it's empty and harmless. -- **`user-dirs.dirs` is tracked directly** as a plain dotfile in the bare repo (not generated/overwritten by `dot setup folders` from a hardcoded table each run) — unlike KDE's rc files (tracked via a separate declarative-manifest mechanism, see the `dot-kde` spec), `user-dirs.dirs` has no volatile/machine-specific fields, so it fits the same direct-tracking treatment as any other plain dotfile (`.bashrc`, etc.). The tracked file is the single source of truth for the desired short names. -- **`dot setup folders` still needs a small hardcoded table** mapping each of the 8 standard XDG categories to its legacy default folder name (`Documents`, `Downloads`, etc.) — this is used purely to locate content left behind by a fresh XDG-defaults install and merge it into the already-tracked short-named target; it is not the source of truth for the target names themselves (that's the tracked `user-dirs.dirs`). -- **Migration safety, per legacy folder**: - - Empty (strict check: any file at all, including dotfiles/metadata like a stray KDE `.directory` file, counts as non-empty) → merge silently, no prompt. - - Non-empty → print what would be moved and require an explicit `--yes` flag before proceeding. No interactive prompt. - - Collisions (a same-named entry exists in both the legacy folder and its short-named target) → use no-clobber semantics (e.g. `mv -n`) so a colliding file is never silently overwritten; report which files were skipped; leave the legacy folder in place (don't remove it) if any collision occurred, rather than deleting a folder that still holds something that couldn't be merged. -- **Post-migration step**: run `xdg-user-dirs-update` (no arguments) once folder moves are complete, to notify running apps/portals via its D-Bus signal. This is safe against the hand-tracked file — `user-dirs.dirs`'s own header documents that local edits are preserved across runs of the tool. - -## Testing Decisions - -- **Guiding principle**: test the folders task's own logic (mapping, empty-vs-non-empty gating, `--yes` behavior, collision handling, idempotency) through the real CLI entry point, not the internals of `mv`/`mkdir` themselves. -- **Primary seam**: full CLI invocation of `dot setup folders` (and bare `dot setup`), run against a scratch `$HOME` per test case — the existing project convention (see `dot install`'s tests). No new seam is introduced. -- **External command handling**: `xdg-user-dirs-update` is faked out via a `PATH`-prepended fake binary that logs its invocation (and exit code), exactly mirroring how `sudo`/`pacman` are faked for `dot install`'s tests. Real `mkdir`/`mv`/`rmdir` run for real against the scratch `$HOME` — no need to fake filesystem operations themselves. -- **Cases to cover**: fresh migration of empty legacy folders (no `--yes` needed); a legacy folder with real content refuses without `--yes` and proceeds with it; the nested `Pictures/Screenshots→pic/screenshots` rename; a stray dotfile (e.g. a fake `.directory`) in an otherwise-"empty" legacy folder still triggers the confirmation gate; a filename collision between legacy and target is skipped (not overwritten), reported, and leaves the legacy folder in place; re-running `dot setup folders` after a clean migration is a no-op (idempotency); bare `dot setup` runs the folders task as part of running everything; `dot setup folders help` prints usage and touches nothing. -- **Prior art**: `tests/dot.fish`'s existing scratch-`$HOME`-plus-`fishtape` pattern, and specifically the fake-`sudo`/fake-`pacman`-via-`PATH` technique used for `dot install`. - -## Out of Scope - -- The **extra groups** task (`dot setup groups` or similar, porting the old `.extra_groups`/`setup_users` behavior) — it will share the same `dot setup` dispatcher and dual-mode (bare-runs-everything vs. named-task) shape decided here, but its own design (group list format, idempotency, etc.) was not addressed in this spec. -- Any KDE-side settings (caps-lock/Escape swap, screenshot keybinds, Lock Session rebind) — covered separately by the `dot-kde` spec/design. -- Removing the currently-existing, now-orphaned `~/Projects` folder — explicitly left alone, not cleaned up by this feature. -- Any `~/.github/README.md` command-table row or `~/.github/keybindings.md` update — not applicable here (no keybind changes), but the README row is still required by the project's standard "adding a subcommand" checklist at implementation time. - -## Further Notes - -- The old bash `setup_folders`'s naive `mv $from/* $to` has a latent bug this design deliberately avoids: an unquoted glob against an empty directory can misbehave, and it has no collision protection at all. The no-clobber-plus-report behavior specified here is a deliberate improvement over the old script's behavior, not a straight port. -- This spec covers only the `folders` task; `dot setup` itself (the dispatcher, `_dot_setup_usage`, wiring into `commands/`, the completions/help-glob duplication point noted in the project's `CLAUDE.md`) needs to exist as scaffolding for this task to attach to, even though its only other planned task (extra groups) is out of scope here. diff --git a/.config/dot/.claude/spec/dot-setup-nvim.md b/.config/dot/.claude/spec/dot-setup-nvim.md deleted file mode 100644 index f9baac8..0000000 --- a/.config/dot/.claude/spec/dot-setup-nvim.md +++ /dev/null @@ -1,46 +0,0 @@ -## Problem Statement - -On a freshly cloned dotfiles checkout (or any machine where `~/.local/share/nvim/lazy/` is empty or stale), `lazy.nvim` only discovers that plugins are missing when `nvim` is actually launched. The first interactive launch then silently spends a long time cloning `nord.nvim`, `nvim-treesitter`, and `render-markdown.nvim` and compiling every `nvim-treesitter` parser listed in `ensure_installed`, with no obvious progress indication in a normal terminal session — it reads as "nvim isn't starting" rather than "nvim is installing plugins." Nothing in `dot` proactively drives this sync, even though the exact plugin versions are already pinned and tracked in `~/.config/nvim/lazy-lock.json`. - -Separately, `nvim-treesitter`'s parser build step has a known race: concurrent parser installs can collide on a relative `tree-sitter--tmp` directory, causing one parser (e.g. `bash`) to fail to compile. Because the compiled `.so` never lands in `~/.local/share/nvim/lazy/nvim-treesitter/parser/`, that parser gets retried (and can fail again) on every subsequent `nvim` launch until it eventually succeeds — a silent, recurring cost with no clear signal to the user that anything is wrong. - -## Solution - -Add an `nvim` task to the `dot setup` family (introduced by the `dot-setup-folders` spec as the general home for idempotent, re-runnable machine-setup tasks). `dot setup nvim` drives a headless `nvim` session that syncs installed plugins to exactly what `lazy-lock.json` already pins, and verifies afterward that every pinned plugin actually landed on disk — turning a silent, ambiguous first-launch stall into an explicit, scriptable, pass/fail setup step. Bare `dot setup` (no task name) runs this alongside `folders` (and any future tasks). - -## User Stories - -1. As the machine owner, I want `dot setup nvim` to install/sync every plugin pinned in `lazy-lock.json` before I ever open `nvim` interactively, so that my first real editing session isn't interrupted by an unexplained multi-second-to-multi-minute stall that looks like a hang. -2. As the machine owner, I want `dot setup nvim` to use the already-tracked `lazy-lock.json` as the source of truth (not re-resolve latest versions), so that a fresh machine ends up with the exact plugin commits I've already vetted, not whatever is newest upstream that day. -3. As the machine owner, I want `dot setup nvim` to exit non-zero and say clearly which plugin(s) failed to install, so that a partial/broken sync is an obvious, actionable failure rather than something I only notice later inside nvim. -4. As the machine owner, I want re-running `dot setup nvim` when everything is already in sync to be a fast no-op that still exits 0, so that it's safe to include unconditionally in `dot setup`'s bare "run everything" mode without slowing down every re-run. -5. As the machine owner, I want to be able to run `dot setup nvim` in isolation (not just as part of bare `dot setup`), so that I can re-sync plugins on their own after e.g. manually editing `lazy-lock.json` or clearing the plugin directory. -6. As the machine owner, I want `dot setup nvim help` to print usage without touching any plugin state, so that it's consistent with every other `dot` subcommand's `help` behavior. - -## Implementation Decisions - -- **Subcommand family**: lives under the `dot setup` dispatcher established by the `dot-setup-folders` spec — same nested-subcommand convention (`help`-then-`argparse`, `_dot_setup_nvim_usage`), same dual-mode shape (bare `dot setup` runs every task; `dot setup nvim` runs just this one). This spec does not re-describe the shared dispatcher scaffolding itself; see `dot-setup-folders.md` for that. -- **Core action**: run `nvim --headless "+Lazy! restore" +qa`. `Lazy! restore` checks out every plugin in the spec to the exact commit recorded in `lazy-lock.json` (installing it first via clone if missing), so it both fixes "missing plugin" and "plugin present but on the wrong commit" in one call. No separate `TSUpdate`/`TSInstall` step is needed: because none of the current plugins (`nord.nvim`, `nvim-treesitter`, `render-markdown.nvim`) declare a lazy-loading trigger (`event`/`cmd`/`ft`), they load eagerly as part of this same headless session, which drives `nvim-treesitter`'s own `ensure_installed` parser-compilation step as a natural side effect — matching what was observed when reproducing the issue. -- **Failure detection**: `nvim`'s process exit code from `--headless ... +qa` does not reliably reflect whether `Lazy! restore` itself succeeded (Lazy reports failures via its own UI/messages, not necessarily the process exit status). `dot setup nvim` must independently verify success after the headless run completes, by checking that every plugin name declared in `lazy-lock.json` has a corresponding directory under `~/.local/share/nvim/lazy/`. Any pinned plugin missing a directory is treated as a failure: print which plugin(s) didn't install and exit non-zero. -- **Parser-compile failures are out of scope for pass/fail**: the `tree-sitter--tmp` collision race affects `nvim-treesitter`'s internal parser build, not the plugin-directory check above (nvim-treesitter's own directory will exist regardless of whether an individual parser compiled). `dot setup nvim`'s success criterion is "all pinned plugins are present," not "all treesitter parsers compiled" — a parser-level compile flake is expected to self-heal on a later `nvim` launch or `:TSUpdate`, per the `Further Notes` in this spec's investigation. Detecting and retrying individual parser build failures is not attempted here. -- **No package-list file**: unlike `dot install`, there's nothing to record — `lazy-lock.json` is already the tracked source of truth, so `dot setup nvim` never writes to it. - -## Testing Decisions - -- **Guiding principle**: test `dot setup nvim`'s own logic (that it invokes `nvim` correctly, that it correctly detects success vs. a missing plugin) through the real CLI entry point, faking only the external `nvim` binary — not real plugin installs, real git clones, or real compilation, which would be slow and network-dependent in tests. -- **Primary seam**: full CLI invocation of `dot setup nvim` (and bare `dot setup`), run against a scratch `$HOME` per test case — the existing project convention (see `dot install`'s and the planned `dot setup folders`' tests). No new seam is introduced. -- **Faking `nvim`**: a `PATH`-prepended fake `nvim` binary, mirroring the fake-`pacman`/fake-`sudo`/fake-`xdg-user-dirs-update` technique already used/planned in `tests/dot.fish`. The fake logs its invocation args (so a test can assert `dot setup nvim` called it with `--headless "+Lazy! restore" +qa`) and, driven by an env var or scratch-`$HOME` fixture, can simulate "all plugins present" vs. "one plugin missing" by controlling whether it creates the expected directories under the scratch `~/.local/share/nvim/lazy/`. -- **Cases to cover**: a successful sync (fake `nvim` creates all pinned plugin directories) exits 0; a plugin missing after the fake run exits non-zero and names the missing plugin; re-running against an already-fully-synced scratch `$HOME` is still a pass (idempotency) without requiring the fake to do anything different; bare `dot setup` runs the `nvim` task alongside `folders`; `dot setup nvim help` prints usage and never invokes the fake `nvim` at all. -- **Prior art**: `tests/dot.fish`'s scratch-`$HOME`-plus-`fishtape` pattern, and specifically the fake-binary-via-`PATH` technique used for `dot install` (and planned for `dot setup folders`'s `xdg-user-dirs-update` fake). - -## Out of Scope - -- The `dot setup` dispatcher scaffolding itself (bare-runs-everything, per-task dispatch, `_dot_setup_usage`) — already specified in `dot-setup-folders.md`; this spec only adds the `nvim` task onto it. -- The `folders` and any future (e.g. `groups`) `dot setup` tasks — unaffected by this spec beyond now running alongside `nvim` in bare `dot setup`. -- Fixing the underlying `nvim-treesitter` `tree-sitter--tmp` race itself (an upstream plugin behavior) — `dot setup nvim` tolerates it rather than working around it. -- Any change to `~/.config/nvim`'s plugin specs, `lazy-lock.json` contents, or which plugins/parsers are installed — this spec only adds a way to proactively sync to what's already pinned. -- A `~/.github/README.md` command-table row — not written here, but required by the project's standard "adding a subcommand" checklist at implementation time. - -## Further Notes - -- This spec grew out of debugging a real "nvim isn't starting" report: the actual cause was an empty `lazy.nvim` plugin directory triggering a full, slow reinstall on first launch, compounded by a `tree-sitter-bash-tmp` mkdir collision that made the `bash` parser fail and re-attempt on every subsequent launch until it happened to succeed. `dot setup nvim` addresses the first (silent first-launch stall) directly; the second (parser race) is a pre-existing upstream flake this spec does not attempt to fix. diff --git a/.config/dot/.claude/spec/gitea-axi-integration.md b/.config/dot/.claude/spec/gitea-axi-integration.md deleted file mode 100644 index d3b3dd3..0000000 --- a/.config/dot/.claude/spec/gitea-axi-integration.md +++ /dev/null @@ -1,56 +0,0 @@ -## Problem Statement - -Today, `to-spec`, `to-tasks`, and `implement` track specs and tasks as local files (`.claude/spec/.md`, `.claude/tasks/-.md`) scoped to a single git working tree. -That means task state and context don't survive across the machine boundary — a spec or task can't be picked up from a different clone, referenced from a PR, or handed to a differently-scoped agent session without manually carrying the files over. -There's also no natural place for `review-uncommitted`'s findings to live once produced, other than the terminal output, which the operator has to capture manually if they want it preserved as a record. - -## Solution - -Once `gitea-axi` (see the companion `gitea-axi` spec) exists, replace the local-file storage in this project's skill-based task-management pipeline with Gitea issues and pull requests: specs and tasks become labeled issues, "readiness" becomes a label state, and implemented work becomes a pull request that `review-uncommitted` comments on directly. -The workflow-specific semantics (label names, state transitions, PR-to-issue linking) live entirely in the skills' own prose, calling `gitea-axi`'s generic primitives — `gitea-axi` itself stays unaware of this project's conventions. - -## User Stories - -1. As the operator, I want `to-spec` to open a Gitea issue containing the spec instead of writing a local file, so that the spec is visible and referenceable outside my local working tree. -2. As the operator, I want the spec issue labeled to mark it ready for task breakdown, so that a later session can find it without me telling it the issue number. -3. As the operator, I want a new session to be able to locate and read a spec issue by its readiness label, so that I can hand off spec-to-task work across sessions without manually passing context. -4. As the operator, I want `to-tasks` to open one Gitea issue per task instead of writing local task files, so that each task is independently discoverable and referenceable the same way the spec is. -5. As the operator, I want each task issue to retain a reference back to its parent spec issue, so that the `spec` traceability that today's local task-file frontmatter provides isn't lost in the move to issues. -6. As the operator, I want `to-tasks` to remove the spec issue's readiness label once tasks are created from it, so that the state machine reflects "spec has already been broken down" and isn't reprocessed. -7. As the operator, I want to ask a new session to implement "the next task" and have it find the right task issue by its readiness label, so that I don't have to look up and paste an issue number myself. -8. As the operator, I want `implement` to read a task issue's full details before starting work, so that it has the same context a local task file would have given it. -9. As the operator, I want `implement` to open a pull request (carrying the implementation commit) once work is done, instead of leaving only an uncommitted or committed local diff, so that the work is reviewable and mergeable through Gitea like any other PR. -10. As the operator, I want `review-uncommitted` to fetch its diff and spec context from the pull request and its linked issue when run in this workflow, so that I don't need a local spec file for it to work against. -11. As the operator, I want `review-uncommitted`'s three-axis findings posted as a comment on the pull request, so that they're visible as a permanent record on the PR itself, not just in my terminal. -12. As the operator, I want the label taxonomy and state machine (spec/task readiness, PR-to-issue linking conventions) to be easy to change later, so that I can iterate on the workflow without touching `gitea-axi`'s code. -13. As the operator, I want PR granularity (one commit vs. several, one task vs. several per PR) decided case-by-case between me and the agent at `implement` time, rather than fixed by a rule baked into the skill. - -## Implementation Decisions - -- Depends on `gitea-axi` existing first (see the companion spec) — this spec only covers how this project's skills consume it, not the tool itself. -- Affected skills: `to-spec`, `to-tasks`, `implement`, `review-uncommitted`. Each swaps its local-file I/O (`Read`/`Write`/`Edit` against `.claude/spec/` and `.claude/tasks/`) for calls to `gitea-axi`'s generic issue/PR primitives. -- `to-spec` opens an issue (instead of writing `.claude/spec/.md`) carrying the same spec content and format, labeled to mark it as newly created and ready for breakdown. -- `to-tasks` reads the spec issue, opens one issue per task slice (instead of `.claude/tasks/-.md`), each carrying a reference back to the parent spec issue (replacing the current `spec` frontmatter field), labels each task issue as ready for implementation, and removes the readiness label from the spec issue once done. -- `implement` locates its target task issue (by number if given, or by readiness label/query if asked for "the next task"), reads it in place of a local task file, does the work, and opens a pull request carrying the implementation commit — in place of just staging locally and leaving the commit to the operator. -- `review-uncommitted` gains a Gitea-aware path: when working against a PR, it fetches PR diff/metadata and the linked spec/task issue instead of `git diff HEAD` and a local spec file, and posts its aggregated Risk/Standards/Spec report as a single PR comment once done (per the companion spec's decision to keep this a single comment, not per-finding inline comments). -- Label taxonomy and exact naming (today referred to provisionally as "spec"/"ready-for-agent") are explicitly left open — to be finalized when these skill updates are actually implemented, not fixed by this spec. -- PR granularity (commits per PR, tasks per PR) is explicitly left as a case-by-case decision made between the operator and the agent at `implement` time — not a fixed rule this spec encodes. - -## Testing Decisions - -- Skills are prose (`SKILL.md` files), not unit-testable code — there is no automated test seam for the skill updates themselves. Verification is behavioral: running each updated skill against a real (or disposable) Gitea instance end-to-end and confirming the resulting issues, PRs, labels, and comments match what the prose describes. -- The one seam that is testable in the traditional sense is `gitea-axi` itself, already covered by the companion spec — these skill updates are downstream consumers of that seam, not a new one. -- No prior art in this repo for testing prompt-based skills; `~/.config/dot/tests/dot.fish` (fishtape, end-to-end against fixtures) is the closest pattern, but it tests code, not prose, so it doesn't transfer directly. - -## Out of Scope - -- Building `gitea-axi` itself (fully covered by the companion `gitea-axi` spec). -- Deciding the actual label taxonomy and state machine names — deferred to implementation time. -- Deciding PR granularity rules — deferred to case-by-case decisions at `implement` time. -- Inline per-finding PR review comments for `review-uncommitted` (deferred enhancement, noted in the companion spec). -- Any change to `codebase-design`, `domain-modeling`, `test-driven-development`, or other skills not in the four listed above. - -## Further Notes - -- This spec assumes `gitea-axi`'s generic primitives (issue create/read/find-by-label/update-labels, PR create/get/comment) are sufficient for the four listed skills. If implementation reveals a missing primitive, it should be added to `gitea-axi` itself (kept generic) rather than special-cased here. -- This is an opinionated, single-adopter view of `gitea-axi` — it intentionally isn't part of the `gitea-axi` spec itself, since that tool is meant to stay usable by others regardless of this project's specific workflow conventions. diff --git a/.config/dot/.claude/spec/gitea-axi.md b/.config/dot/.claude/spec/gitea-axi.md deleted file mode 100644 index a95698f..0000000 --- a/.config/dot/.claude/spec/gitea-axi.md +++ /dev/null @@ -1,70 +0,0 @@ -## Problem Statement - -Coding agents that need to drive a Gitea-hosted workflow (issues, pull requests, labels) today have two poor options. -The official `tea` CLI is human-oriented: it has no token-efficiency, no contextual guidance, and no agent-facing error conventions. -Gitea's MCP servers expose the full API surface (dozens of tools) rather than being tuned for token or turn efficiency. -There is no Gitea-focused tool built to the same "agent ergonomics" standard that `gh-axi` established for GitHub. - -## Solution - -Build `gitea-axi`: a thin, generic CLI wrapper around the official `tea` binary that reshapes its output according to the 10 AXI (Agent eXperience Interface) principles — token-efficient output, minimal default schemas, structured errors, contextual next-steps, and so on. -It gives coding agents an ergonomic, low-token way to drive issues and pull requests on any Gitea instance. -It ships both as an installable npm CLI and as an installable Agent Skill, so any agent session can adopt it with one install step. - -## User Stories - -1. As a coding agent, I want to create a Gitea issue with a title, body, and labels, so that I can record work items for later retrieval. -2. As a coding agent, I want to find issues by label (and other basic filters), so that I can locate relevant work without already knowing its issue number. -3. As a coding agent, I want to read an issue's full body, labels, and comments, so that I can load its context into a session. -4. As a coding agent, I want to add and remove labels on an existing issue, so that I can reflect state transitions as work progresses. -5. As a coding agent, I want to create a pull request from the current branch, so that completed work becomes reviewable. -6. As a coding agent, I want to fetch a pull request's metadata and diff, so that review tooling can operate on it without re-deriving it from git. -7. As a coding agent, I want to post a comment on a pull request, so that findings or notes are visible as a permanent reference on the PR itself. -8. As a coding agent, I want command output in a token-minimized format (TOON, minimal default fields, truncated large fields with an escape hatch), so that repeated calls across a long-running session don't consume excessive context. -9. As a coding agent, I want pre-computed aggregates in list/read output, so that I don't need follow-up calls just to derive obvious derived fields. -10. As a coding agent, I want explicit empty-state output when a query returns nothing, so that "no results" is never ambiguous with an error or a hang. -11. As a coding agent, I want structured errors with actionable suggestions and meaningful exit codes instead of prose failures, so that I can self-correct without the operator's help. -12. As a coding agent, I want mutations to be idempotent and to never prompt interactively, so that unattended, scripted use never stalls or double-applies. -13. As a coding agent, I want contextual next-step suggestions appended after output, so that I know what to call next without being taught the tool from scratch every session. -14. As a coding agent, I want a consistent per-subcommand `--help`, so that I can discover the interface on demand rather than needing it pre-loaded in context. -15. As an operator, I want gitea-axi run with no arguments to show live, actionable repository state instead of a help screen, so that I get immediate value without memorizing flags. -16. As an operator, I want gitea-axi to reuse my existing `tea` login configuration (including multi-instance profiles), so that I don't manage a second set of credentials. -17. As an operator, I want gitea-axi's command surface to stay generic, with no workflow-specific behavior baked in, so that it's useful across different projects and label/workflow conventions without code changes. -18. As an operator, I want gitea-axi published to npm and as an installable Agent Skill, so that I (and others) can adopt it with a single install step. - -## Implementation Decisions - -- New standalone repository — not bundled into any other tool or CLI framework. -- Developed against the operator's personal Gitea instance; push-mirrored to GitHub for npm publishing and public discoverability/contribution. -- Language/runtime: TypeScript on Node, matching the `gh-axi` reference implementation this design is modeled on. -- Implementation strategy: wrap the `tea` binary as a subprocess, invoking it with `--output json` (or the most structured format it supports) and reshaping that output — not a from-scratch Gitea API client. This reuses `tea`'s auth, multi-instance login, and full command coverage for free. - - **Flagged risk**: subprocess-wrapping-a-CLI can become fragile or slow at higher call volumes or in edge cases (partial output, non-JSON error text, version drift in `tea`'s own output shape). If this proves to be a real problem in practice, the fallback is a direct Gitea HTTP API client (as Gitea's own MCP server already does) — noted here so it isn't re-litigated from scratch if revisited. -- Auth: no independent credential handling. Every command shells out through `tea`, so it relies entirely on `tea login add` already being configured, including `tea`'s own `--login`/multi-instance profile resolution. -- Command surface: generic Gitea primitives only — issue create/read/find-by-label/update-labels, PR create/get/comment (see User Stories above for the full list). No project-specific or workflow-specific commands (e.g. nothing that hardcodes a particular label taxonomy or state machine). -- Output ergonomics follow the 10 AXI principles (https://axi.md/, https://github.com/kunchenguid/axi), grouped as: - - Efficiency: TOON-formatted stdout (~40% fewer tokens than JSON), minimal default schemas (3-4 fields per list item), truncated large fields with size hints and an escape hatch to fetch full content. - - Robustness: pre-computed aggregates to avoid round trips, explicit empty-state messages, structured errors and exit codes, idempotent mutations, no interactive prompts, fail loudly on unknown flags. - - Discoverability: opt-in session integration plus an on-demand skill, no-args shows live data rather than help text, contextual next-step suggestions appended after output. - - Help: consistent per-subcommand `--help`. -- Distribution: published to npm as a global-installable CLI, and packaged as an installable Agent Skill (installable the same way as `gh-axi`'s, e.g. via `npx skills`) — both built together from the start, not phased. - -## Testing Decisions - -- Good tests exercise the actual command-line interface (argv in, stdout/exit-code out) — the one seam every caller depends on — not internal functions, and not a mock of the `tea` subprocess call itself (that would only prove gitea-axi calls `tea` with certain arguments, not that the output is correctly reshaped). -- Tests should run the real, built CLI against either a disposable/fixture Gitea instance or a recorded fixture of `tea`'s own JSON output. -- Prior art: `~/.config/dot/tests/dot.fish` tests `dot`'s subcommands end-to-end with fishtape, building a throwaway bare-git remote fixture per scenario rather than mocking `git`. The equivalent here is a disposable Gitea fixture (or recorded `tea` output) rather than mocking `tea`. - -## Out of Scope - -- Any workflow-specific commands or hardcoded label/state semantics (tracked separately — see the companion `gitea-axi-integration` spec for one concrete adopter's usage). -- Inline per-line PR review comments (a possible future addition; the primitive here is a plain PR comment). -- A from-scratch Gitea HTTP API client bypassing `tea` (deferred fallback if the subprocess-wrapping approach proves fragile — see flagged risk above). -- Multi-instance orchestration beyond what `tea`'s own login profiles already provide. -- A `dot` (or any other host CLI's) subcommand wrapping this tool — it is intentionally a standalone, independently distributed tool. - -## Further Notes - -- AXI ("Agent eXperience Interface") is an existing framework: https://axi.md/ and https://github.com/kunchenguid/axi. Its reference implementation, `gh-axi` (https://github.com/kunchenguid/gh-axi), wraps GitHub's `gh` CLI the same way this spec proposes wrapping `tea`, and reports (its own benchmarks) 100% task success vs. 86% for raw `gh`, and 66% cheaper / 74% fewer input tokens / half the interaction turns vs. GitHub's official MCP server on the same 17-task benchmark. -- The official Gitea MCP server (https://gitea.com/gitea/gitea-mcp) was evaluated and rejected as the primary approach: roughly 45 consolidated tools, actively maintained, but — by analogy to the gh-axi-vs-GitHub-MCP benchmark — generic MCP servers expose the full API surface rather than being tuned for token/turn efficiency, and using one directly would forfeit control over output shape. -- Raw `tea` was also evaluated and rejected as the long-term approach (though it remains the dependency this tool wraps): it already supports `--output json/yaml/csv/tsv`, so it's scriptable, but its schemas are human-oriented, not agent-ergonomic (no truncation, no contextual next-steps, no token minimization). -- Name collision check (as of this writing): `gitea-axi` is unclaimed on both npm and GitHub. diff --git a/.config/dot/.claude/tasks/0000-nested-subcommand-discovery.md b/.config/dot/.claude/tasks/0000-nested-subcommand-discovery.md deleted file mode 100644 index cc823b9..0000000 --- a/.config/dot/.claude/tasks/0000-nested-subcommand-discovery.md +++ /dev/null @@ -1,31 +0,0 @@ ---- -spec: dot-kde ---- - -## What to build - -Extend the subcommand-discovery mechanism to glob one directory level -deeper, so a `dot` subcommand can live as `commands//.fish` -alongside a companion file (e.g. a Python helper), not just as a flat -`commands/.fish`. This mechanism exists in two places today -(`dot.fish`'s `__dot_help` and `completions/dot.fish`'s -`__dot_custom_subcommands`), intentionally duplicated rather than shared -(fish autoload constraints) — both must be updated together and stay in -sync. Existing flat-file subcommands must keep working unchanged. - -This is pure prefactoring: no KDE-specific behavior is introduced here. - -## Acceptance criteria - -- [x] `dot help` lists a subcommand that lives at `commands//.fish` -- [x] `dot ` sources and dispatches to `commands//.fish`'s `_dot_` function -- [x] Tab-completion (`__dot_custom_subcommands`) lists a nested-directory subcommand -- [x] Existing flat-file subcommands (`dot install`) are still discovered and dispatched correctly -- [x] `tests/dot.fish` covers a nested-directory dummy command dispatching correctly, alongside the existing flat-file dispatch case - -## Implementation Notes - -- The dispatch check in `dot.fish` tries the flat file first, then falls back to `commands//.fish` — a flat file always wins if both somehow exist for the same name. -- The nested-directory scan requires the file basename to match its containing directory's name (`commands/foo/foo.fish`), not just any `.fish` file one level deep — this matches the acceptance criteria's exact convention and avoids misclassifying a stray companion file (e.g. a `.py` helper) as its own subcommand. -- Tab-completion's nested-directory listing was verified manually (sourcing `completions/dot.fish` and calling `__dot_custom_subcommands` directly) rather than via an automated test — `tests/dot.fish` has no existing infrastructure for testing completions at all, even for pre-existing flat commands, so adding one here would be out of scope for this prefactoring task. -- Updated `CLAUDE.md`'s "Architecture" and "Adding a subcommand" sections to document the new nested-directory convention, since it previously only described the flat-file dispatch contract. diff --git a/.config/dot/.claude/tasks/0001-kde-schema-backed-save.md b/.config/dot/.claude/tasks/0001-kde-schema-backed-save.md deleted file mode 100644 index 169a12c..0000000 --- a/.config/dot/.claude/tasks/0001-kde-schema-backed-save.md +++ /dev/null @@ -1,63 +0,0 @@ ---- -spec: dot-kde -blocked-by: 0000-nested-subcommand-discovery ---- - -## What to build - -Stand up `dot kde` itself: the fish dispatcher plus its Python helper, -living together under `commands/kde/` per the nested-subcommand layout -from the prior task. Establish the manifest file (flat text file directly -under `~/.config/dot/`, one `identifier=value` line each, split on the -first `=` only; identifier split on the first two `.`s into -`file.group.key`, leaving the key free to contain further dots or spaces). - -Implement the KConfigXT schema-backed mechanism: reads and writes go -through `kreadconfig6`/`kwriteconfig6`, and the "default" value for a -setting comes from its `.kcfg` schema. Build the `(rcfile → [kcfg files])` -mapping table by scanning the system's KConfigXT schema directory for -files that statically declare their target rc file -(``), plus a small hand-maintained list for the -exceptions that only declare their target file at runtime -(`` — `kwin.kcfg` is a known example). The schema -directory location must be overridable (e.g. via an environment variable), -defaulting to the real system path, so tests can point it at a fixture -directory of synthetic `.kcfg` files instead. - -Structure identifier resolution as a dispatchable decision (rc file is -`kglobalshortcutsrc` → shortcuts; else resolves via the mapping table → -schema-backed; else → freeform) even though only the schema-backed branch -is implemented yet — later tasks add the other two branches without -restructuring this. - -Implement `dot kde save` for schema-backed settings, in both modes: -run with no arguments, refresh every already-declared manifest entry's -value from the live system; run with an explicit identifier, read its -current live value and add it to the manifest as a new declared entry. -Add `dot kde help` and `dot kde save help`, following the project's -check-for-`help`-before-`argparse` convention at each dispatch level. - -Add README rows for `dot kde help`, `dot kde save `, and -`dot kde save` (no arguments). - -## Acceptance criteria - -- [x] `dot kde` and `dot kde save` are discoverable via `dot help` and dispatch correctly -- [x] Manifest parsing splits correctly on the first `=` (values may contain `=`) and the first two `.`s of the identifier (keys may contain dots/spaces) -- [x] The `(rcfile → [kcfg files])` mapping table is derived by scanning a schema directory for ``, plus the hand-maintained exceptions list for `arg="true">` schemas -- [x] The schema directory is overridable via an environment variable, defaulting to the real system path -- [x] `dot kde save ` reads the current live value via `kreadconfig6` and adds a new declared entry to the manifest -- [x] `dot kde save` with no arguments refreshes every already-declared manifest entry's stored value from the live system, leaving undeclared settings untouched -- [x] `dot kde help` and `dot kde save help` print usage without touching the manifest or invoking `kreadconfig6`/`kwriteconfig6` -- [x] Tests run against a scratch `$HOME` and a fixture `.kcfg` schema directory, exercising manifest read/write, identifier parsing, and mapping-table-driven default lookup, per the project's scratch-`$HOME`-plus-`fishtape` convention -- [x] README has rows for `dot kde help`, `dot kde save `, and `dot kde save` - -## Implementation Notes - -- File layout: `commands/kde/kde.fish` (thin dispatcher: help-before-dispatch at the `dot kde` level, then hands off to the Python helper) plus `commands/kde/kde.py` (manifest parsing, mapping-table derivation, mechanism resolution, `kreadconfig6` invocation, and `save`'s own help-before-work check). -- Manifest location: `~/.config/dot/kde-manifest`, a flat file directly under `~/.config/dot/` as specified. -- Mechanism dispatch (`resolve_mechanism`) implements all three branches described in the parent spec (shortcuts / schema / freeform) even though only `schema` is wired to real behavior; `shortcuts` and `freeform` both currently raise a clear "not yet supported" error from `save_one`, so later tasks can fill them in without restructuring the dispatch. -- Test fixtures added under `tests/fixtures/kcfg/`: `testrc.kcfg` (a plain `` schema, including an entry whose ini `key=` differs from its schema `name=`, and one entry whose key contains dots and spaces), `kwin.kcfg` (an `arg="true"` schema resolved only via the hand-maintained exceptions list), and `unmapped.kcfg` (an `arg="true"` schema absent from that list, proving it's never guessed at from its own filename). -- Per the project's testing convention, `kreadconfig6` is never mocked for the tests exercising actual `save` behavior — it runs for real against fixture rc files under a scratch `$HOME`. It's faked (via a `$PATH`-prepended logging stub) only for the two tests asserting that `dot kde help` / `dot kde save help` never invoke it. -- Applied two small cleanups surfaced by `/review-uncommitted`'s Standards pass before closing out: extracted a shared `_parse_kcfg` helper (was duplicated between `build_kcfg_map` and `find_schema_default`), and introduced a `Setting = namedtuple("Setting", ["file", "group", "key"])` to stop threading those three strings as separate parameters across `resolve_mechanism`/`find_schema_default`/`read_live_value`/`save_one`. -- The Spec pass caught that the `unmapped.kcfg` fixture was created but never actually exercised by a test; added a case asserting `dot kde save unmapped.Whatever.Setting` resolves to freeform rather than schema-backed. diff --git a/.config/dot/.claude/tasks/0002-kde-schema-backed-apply.md b/.config/dot/.claude/tasks/0002-kde-schema-backed-apply.md deleted file mode 100644 index f14fcf0..0000000 --- a/.config/dot/.claude/tasks/0002-kde-schema-backed-apply.md +++ /dev/null @@ -1,32 +0,0 @@ ---- -spec: dot-kde -blocked-by: 0001-kde-schema-backed-save ---- - -## What to build - -Implement `dot kde apply` for schema-backed settings: read every entry in -the manifest and write its declared value onto the live system via -`kwriteconfig6`. Re-running it against an already-applied system must be a -no-op with no unintended side effects — this is the idempotence the -feature depends on for safe re-runs after a KDE update or on a freshly -built machine. Add `dot kde apply help`, following the project's -check-for-`help`-before-`argparse` convention. - -Add a README row for `dot kde apply`. - -## Acceptance criteria - -- [x] `dot kde apply` pushes every manifest entry's declared value onto the live system via `kwriteconfig6` -- [x] Re-running `dot kde apply` against a system already matching the manifest changes nothing (idempotent) -- [x] `dot kde apply help` prints usage without writing anything -- [x] Tests run against a scratch `$HOME`, exercising apply over a manifest with schema-backed entries, verifying resulting rc-file contents and idempotence on a second run -- [x] README has a row for `dot kde apply` - -## Implementation Notes - -- File layout mirrors `save`'s: `write_live_value` (the `kwriteconfig6` counterpart to `read_live_value`) and `apply_one` (mirroring `save_one`'s `parse_identifier` → `resolve_mechanism` → schema-only gate) added to `commands/kde/kde.py`; `cmd_apply` mirrors `cmd_save`'s help/argument/error-handling scaffold. `kde.fish` gained an `apply` dispatch case above `save`. -- `apply` takes no arguments (unlike `save`, which supports an optional identifier) — the task only specifies pushing the whole manifest, and the parent spec's `apply` user story has no per-identifier mode, so `dot kde apply ` is rejected as misuse rather than silently ignored. -- `write_live_value` passes the value positionally after a `--` separator (`kwriteconfig6 --file ... --group ... --key ... -- `) rather than via a `--value` flag, since `kwriteconfig6` takes the value as a mandatory positional argument, not a flag; `--` guards against a value that itself looks like an option. -- Non-schema (shortcuts/freeform) manifest entries are rejected with the same "not yet supported" error `save_one` already raises for those mechanisms, kept out of scope per this task's title ("...apply for schema-backed settings"); those mechanisms are added in later tasks (0004, 0005) without needing to restructure `cmd_apply`. -- `/review-uncommitted` flagged two baseline duplication smells (`apply_one`/`cmd_apply` mirroring `save_one`/`cmd_save`'s shape) and one observation (a failing entry mid-manifest halts `apply` immediately, leaving earlier writes already applied — a partial-apply state, untested either way). Left as-is: the duplication mirrors an already-established local convention from task 0001 rather than introducing a new one, and the partial-apply behavior is consistent with `cmd_save`'s pre-existing control flow, not a new risk introduced by this task. diff --git a/.config/dot/.claude/tasks/0003-kde-schema-backed-diff.md b/.config/dot/.claude/tasks/0003-kde-schema-backed-diff.md deleted file mode 100644 index caef2fa..0000000 --- a/.config/dot/.claude/tasks/0003-kde-schema-backed-diff.md +++ /dev/null @@ -1,35 +0,0 @@ ---- -spec: dot-kde -blocked-by: 0001-kde-schema-backed-save ---- - -## What to build - -Implement `dot kde diff`'s broad, read-only scan for schema-backed -settings: walk every `(rcfile, group, key)` reachable through the -mapping table built in the prior task, compare each live value -(`kreadconfig6`) against its schema-declared default, and report every -mismatch. Each reported mismatch is tagged as declared (its identifier is -present in the manifest — an intentional, already-tracked deviation) or -undeclared (never explicitly declared). `diff` never writes anything. -Add `dot kde diff help`, following the project's -check-for-`help`-before-`argparse` convention. - -Add a README row for `dot kde diff`. - -## Acceptance criteria - -- [x] `dot kde diff` reports every schema-backed setting whose live value differs from its schema-declared default -- [x] Each reported mismatch is tagged declared or undeclared based on manifest presence -- [x] `dot kde diff` makes no writes under any circumstances -- [x] `dot kde diff help` prints usage without scanning -- [x] Tests run against a scratch `$HOME` and fixture `.kcfg` schema directory, covering: a declared mismatch, an undeclared mismatch, and a setting matching its default (not reported) -- [x] README has a row for `dot kde diff` - -## Implementation Notes - -- `cmd_diff` (in `commands/kde/kde.py`) reuses `build_kcfg_map`/`iter_schema_identifiers` (already built for `kde.py complete`) to walk every schema-backed `(rcfile, group, key)`, then `find_schema_default`/`read_live_value` (already built for `save`) to compare live vs. default. No new scanning machinery was needed — this task's whole job was wiring existing pieces together into a read-only report. -- Output format: one line per mismatch, ` = (default: )`. Not specified by the task, so chosen to read clearly and stay unambiguous under substring matching in tests (avoided bracketed tags like `[declared]`, since fish's `string match` glob treats `[...]` as a character class). -- `/review-uncommitted`'s Spec pass caught that `cmd_diff` had no error handling around `read_live_value`, unlike `cmd_apply`/`cmd_save`'s `try/except (ValueError, RuntimeError)` — a single `kreadconfig6` failure would have aborted the entire broad scan with an uncaught traceback, contradicting `diff`'s "report every mismatch" framing. Fixed: `cmd_diff` now catches `RuntimeError` per-identifier, prints a warning to stderr, and continues scanning the rest. -- The Standards pass flagged the "build map → iterate `sorted(set(iter_schema_identifiers(...)))`" shape as now duplicated between `cmd_diff` and `cmd_complete`, and the new test scenarios' fixture boilerplate as repeating the `apply` tests' shape almost verbatim. Left both as-is: the loop duplication is two call sites doing genuinely different things with the result, and the test boilerplate matches this file's already-established per-scenario convention (each scenario resets `$HOME` independently) rather than introducing a new pattern. -- Post-closeout fix (user-reported): `~/.config/fish/completions/dot.fish`'s `dot kde` completion block only ever listed `save`/`help` as verbs — `apply` was never added when task 0002 built it, and this task initially repeated the same omission for `diff`. Fixed both by adding `apply` and `diff` to the top-level verb-offering line and to the post-subcommand `help` gating; verified manually via `complete -C"dot kde "` and `complete -C"dot kde apply "`/`complete -C"dot kde diff "`. diff --git a/.config/dot/.claude/tasks/0004-kde-freeform-mechanism.md b/.config/dot/.claude/tasks/0004-kde-freeform-mechanism.md deleted file mode 100644 index 1fb5b57..0000000 --- a/.config/dot/.claude/tasks/0004-kde-freeform-mechanism.md +++ /dev/null @@ -1,41 +0,0 @@ ---- -spec: dot-kde -blocked-by: [0002-kde-schema-backed-apply, 0003-kde-schema-backed-diff] ---- - -## What to build - -Add the freeform mechanism as a dispatch branch across `save`, `apply`, -and `diff`: for settings with no KConfigXT schema (e.g. `kxkbrc`'s -`Options=` line), read and write via `kreadconfig6`/`kwriteconfig6`, with -"default" defined as "the key is absent" rather than any schema-declared -value. In the identifier-resolution decision from the first schema-backed -task, this is the fallback branch: an identifier whose `(rcfile, group, -key)` doesn't resolve through the mapping table is freeform. Because -there's no schema to enumerate, freeform settings can only be checked by -`diff` when already declared in the manifest — they never participate in -undeclared broad-scan discovery. - -As the real-world validation for this task, bring the machine's live, -already-hand-set `kxkbrc` caps-lock/Escape swap -(`Options=caps:escape_shifted_capslock`) under tracking via -`dot kde save`, and confirm `dot kde apply`/`dot kde diff` behave -correctly against it. - -## Acceptance criteria - -- [x] An identifier whose `(rcfile, group, key)` has no schema match is treated as freeform rather than erroring -- [x] `dot kde save ` and `dot kde save` (refresh) work for freeform entries -- [x] `dot kde apply` writes freeform entries via `kwriteconfig6`, idempotently -- [x] `dot kde diff` reports a freeform mismatch when its identifier is already declared in the manifest, and never surfaces an undeclared freeform setting via broad scan -- [x] Tests run against a scratch `$HOME`, covering freeform save/apply/diff using a fixture rc file with no corresponding schema -- [x] The live `kxkbrc` caps-lock/Escape swap is tracked via `dot kde save` and the manifest committed to the dotfiles repo - -## Implementation Notes - -- `save_one`/`apply_one`'s gate changed from `mechanism != "schema"` (reject everything but schema) to `mechanism == "shortcuts"` (reject only shortcuts) — freeform now flows through the same `read_live_value`/`write_live_value` calls schema-backed settings already use, since both mechanisms only differ in what "default" means, not in how the read/write itself happens. -- `cmd_diff` gained a second pass after the existing schema broad-scan: it walks the manifest (not the kcfg mapping table, which freeform settings are absent from by definition), resolves each identifier's mechanism, and reports only those that resolve to `freeform` and whose live value is non-empty — structurally guaranteeing freeform can never surface via undeclared broad scan, since the loop never sees anything outside the manifest. -- **Real-world validation surfaced a stale premise**: the task assumed the caps-lock/Escape swap was "already hand-set" and live, but the machine had no `kxkbrc` file and no active XKB option at all. Confirmed with the user before proceeding; with their approval, wrote the option live via `kwriteconfig6 --file kxkbrc --group Layout --key Options -- caps:escape_shifted_capslock` and applied it immediately via a live KWin reconfigure (`busctl --user call org.kde.KWin /KWin org.kde.KWin reconfigure`), then ran `dot kde save kxkbrc.Layout.Options` to bring it under tracking. `dot kde apply`/`dot kde diff` were both verified against the real entry (idempotent apply; diff reports `declared kxkbrc.Layout.Options = caps:escape_shifted_capslock (default: )`). -- Added a `.github/keybindings.md` row for the swap (`CapsLock` → `Esc`, `Shift`+`CapsLock` → real Caps Lock toggle), per the project's cross-cutting keybindings convention. -- Existing tests that previously asserted freeform saves/applies were *rejected* (written when freeform was still unimplemented, per task 0001/0002's "not yet supported" stopgap) were updated to assert success instead, using a new `somefreeform` fixture rc file with no corresponding `.kcfg` schema. Coverage for the still-unimplemented shortcuts mechanism (task 0005) was added in the same spots to keep the "not yet supported" rejection path tested now that freeform no longer exercises it. -- `/review-uncommitted`'s Spec pass caught that `cmd_diff`'s new freeform loop called `parse_identifier` on raw manifest keys with no exception guard, unlike the rest of the function — a hand-edited manifest with a malformed identifier would have crashed the whole scan instead of reporting a clean per-identifier error. Fixed: the loop body is now wrapped in `try/except (ValueError, RuntimeError)`, matching the file's established per-identifier-failure-tolerant convention. The Standards pass also flagged threading a hardcoded `None`/blank literal through the freeform loop instead of the real `default` value returned by `resolve_mechanism`; fixed by reusing that variable directly (`default or ''` for display, since freeform's default is always `None`). diff --git a/.config/dot/.claude/tasks/0005-kde-shortcuts-mechanism.md b/.config/dot/.claude/tasks/0005-kde-shortcuts-mechanism.md deleted file mode 100644 index 66c53d0..0000000 --- a/.config/dot/.claude/tasks/0005-kde-shortcuts-mechanism.md +++ /dev/null @@ -1,57 +0,0 @@ ---- -spec: dot-kde -blocked-by: [0002-kde-schema-backed-apply, 0003-kde-schema-backed-diff] ---- - -## What to build - -Add the shortcuts mechanism as a dispatch branch across `save`, `apply`, -and `diff`: identifiers rooted at `kglobalshortcutsrc` are resolved not by -editing the rc file directly but through KDE's `kglobalaccel` D-Bus -service — `shortcut(actionId)` for the current value, `defaultShortcut -(actionId)` for the default, and `setShortcut(actionId, keys, flags)` -with `flags = NoAutoloading` for writes (so a declared value always wins -over any previously saved shortcut). `actionId` is the 4-element -`[componentUnique, actionUnique, componentFriendly, actionFriendly]` -tuple; only the two `Unique` fields are stored in the manifest, and the -two friendly-name fields are resolved dynamically at call time by looking -up the component's shortcut list. - -Per the spec's testing decisions, this mechanism is deliberately excluded -from the automated test suite (it depends on a live, already-running -session service that isn't practically substitutable without disproportionate -mock infrastructure) — verify it manually against the real session instead. - -As the real-world validation, apply the planned screenshot/session-lock -keybind changes (Spectacle bindings, moving Lock Session off `Meta+L` to -`Meta+X`) through `dot kde save`/`dot kde apply`, and update the -corresponding rows in `keybindings.md` in the same change, per the -project's cross-cutting keybindings convention. - -## Acceptance criteria - -- [x] An identifier whose rc file is `kglobalshortcutsrc` dispatches to the `kglobalaccel` D-Bus mechanism rather than the schema-backed or freeform paths -- [x] `dot kde save ` and `dot kde save` (refresh) read a shortcut's current value via `shortcut(actionId)`, resolving the friendly-name fields dynamically -- [x] `dot kde apply` writes a declared shortcut via `setShortcut(actionId, keys, NoAutoloading)`, verified manually to take effect immediately in the running session -- [x] `dot kde diff` reports a declared shortcut mismatch by comparing against `defaultShortcut(actionId)`, verified manually -- [-] The Spectacle and Lock-Session (`Meta+X`) keybind changes are applied through `dot kde save`/`apply` and tracked in the manifest -- [x] `keybindings.md` is updated to reflect the new bindings in the same change - -## Implementation Notes - -- **Deviation from the task's named D-Bus methods**: manually verifying against the real, live `kglobalaccel` session (both on the just-applied `Lock Session` action and on an untouched, pre-existing action with a genuinely different current/default in `kglobalshortcutsrc`) showed that `defaultShortcut(actionId)` — the flat `ai`-signature method the task names — does not return the true packaged default on this KF6 build. - It just mirrors `shortcut(actionId)`. - Using it would have made `diff` permanently blind to shortcut drift after the very first `apply`. - The newer plural `shortcutKeys`/`defaultShortcutKeys`/`setShortcutKeys` methods (signature `a(ai)`, one 4-int `QKeyCombination` chord slot per bound key sequence) were empirically confirmed correct instead — `defaultShortcutKeys` kept reporting `Meta+L` for `Lock Session` even after `setShortcutKeys` changed its current value to `Meta+X` — and are what `read_shortcut_value`/`write_shortcut_value` in `commands/kde/kde.py` actually call. - `NoAutoloading`'s value (`0x4`, from `KF6/KGlobalAccel/kglobalaccel.h`) is unchanged by this swap. -- Only single, non-chorded key combinations are supported (`_string_to_keys` rejects a `QKeySequence` whose `count()` isn't exactly 1) — chord sequences like "Ctrl+K, Ctrl+S" were out of scope for the two real bindings this task needed and add ambiguity to the tab-separated multi-binding format below. -- **Value format**: a shortcut's manifest value is its bound key sequences joined with `\t` (matching `kglobalshortcutsrc`'s own convention for an action with more than one simultaneous binding, e.g. `Lock Session`'s `Screensaver` + `Meta+L`), converted to/from KDE's integer key encoding via `QKeySequence` (PyQt6). - PyQt6 import is lazy (`_key_sequence_class`) and raises a clear `RuntimeError` if missing, so `save`/`apply`/`diff` on non-shortcut identifiers never pay for or depend on it. -- **Spectacle bindings dropped** from this change's real-world validation. - Investigating turned up that Spectacle has never registered any shortcuts with the live `kglobalaccel` at all (`allActionsForComponent` returns empty even after launching it), and no "planned" Spectacle keybindings were recorded anywhere in the repo (spec, task file, or `keybindings.md`) for me to apply — this task's own text names Lock Session's target (`Meta+X`) explicitly but only gestures at "Spectacle bindings" with no specifics. - Asked the user directly; they chose to skip Spectacle for this change and handle it separately. - Only the Lock Session move is applied here. - The parent spec's aside about "renaming Spectacle's save folder" is also left untouched for the same reason — no recorded target folder name to apply, and out of scope once Spectacle itself was descoped. -- **Lock Session validation**: `dot kde save "kglobalshortcutsrc.ksmserver.Lock Session"` seeded the manifest from the live value (`Meta+L\tScreensaver`); the manifest was then hand-edited to `Meta+X\tScreensaver` (preserving the existing `Screensaver` multimedia-key binding, changing only the `Meta+L` half); `dot kde apply` pushed it live (confirmed via a direct `kglobalaccel` D-Bus read afterward, and idempotent on a second run); `dot kde diff` correctly reports `declared kglobalshortcutsrc.ksmserver.Lock Session = Meta+X\tScreensaver (default: Meta+L\tScreensaver)`. - `Meta+X` is now live and tracked; `keybindings.md` has a row for it. -- Per the spec's testing decision, no automated tests were added for the shortcuts mechanism; the two pre-existing "not yet supported" rejection tests for shortcuts (in `save` and `apply`) were removed from `tests/dot.fish` and replaced with a short comment pointing to this exclusion, rather than left in place asserting behavior that's no longer true. diff --git a/.config/dot/.claude/tasks/0006-setup-dispatcher-and-folders-core.md b/.config/dot/.claude/tasks/0006-setup-dispatcher-and-folders-core.md deleted file mode 100644 index 6b638c1..0000000 --- a/.config/dot/.claude/tasks/0006-setup-dispatcher-and-folders-core.md +++ /dev/null @@ -1,90 +0,0 @@ ---- -spec: dot-setup-folders ---- - -## What to build - -A new `dot setup` subcommand family, following the project's existing -nested-subcommand dispatch convention. Bare `dot setup` (no arguments) runs -every machine-setup task unconditionally; `dot setup ` runs just that -one task. The only task that exists yet is `folders`. - -`dot setup folders` brings the 8 standard XDG user directories under the -project's short-name convention (`Desktop→.desktop`, `Documents→doc`, -`Downloads→dwn`, `Music→mus`, `Pictures→pic`, `Videos→vid`, `Templates` and -`Public` both →`.ignoreme`). The desired short names live in a tracked -`user-dirs.dirs` file (a plain dotfile, not generated from a table each run). -A separate small hardcoded table maps each of the 8 standard XDG categories -to its legacy full-named folder, used only to locate content an XDG-defaults -install would have left behind, and merge it into the already-tracked -short-named target. - -This slice covers the core happy path: a legacy folder found empty (strictly: -no entries at all, including dotfiles/metadata) is merged into its -short-named target silently, with no confirmation needed. As part of the same -`Pictures→pic` pass, a nested `Screenshots` folder is renamed to lowercase -`screenshots`, landing at `pic/screenshots`. After all folder moves complete, -run `xdg-user-dirs-update` (no arguments) once to notify running apps/portals. -`~/wrk` gets no XDG variable of its own and is out of scope for any mapping; -the existing ad hoc `~/Projects` folder is left alone. - -Non-empty legacy folders and filename collisions are out of scope for this -slice (covered by later tasks) — for now it's acceptable for a non-empty -legacy folder to be handled in whatever minimal way unblocks the empty-folder -path (e.g. left untouched with a message), since the confirmation gate and -collision safety are built out next. - -Wire the new command into the project's standard subcommand checklist: a -`_dot_setup_usage` help function reachable via `dot setup help` (and -`dot setup folders help` for the nested task), the completions/help-glob -duplication point, and a README command-table row. - -## Acceptance criteria - -- [x] `dot setup folders` on a fresh scratch `$HOME` (all 8 legacy folders - present and empty) renames them to their short-name targets per the - mapping table, including `Pictures/Screenshots→pic/screenshots`, and - leaves the tracked `user-dirs.dirs` short names in place -- [x] The fake `xdg-user-dirs-update` (PATH-prepended, logging its invocation - per the project's existing fake-`sudo`/fake-`pacman` testing pattern) - is invoked exactly once after a successful migration -- [x] Bare `dot setup` on a fresh scratch `$HOME` runs the `folders` task as - part of running everything -- [x] `dot setup folders help` and `dot setup help` print usage and make no - filesystem changes -- [x] Re-running `dot setup folders` after a clean migration is a no-op - (idempotent) -- [x] `~/.github/README.md` has a command-table row for `dot setup` - (and its `folders` task) with paths relative to `$HOME` -- [x] `~/.config/dot/tests/dot.fish` covers the above cases and - `fishtape ~/.config/dot/tests/dot.fish` passes - -## Implementation Notes - -- The desired short names for `dot setup folders` are read directly from the - tracked `~/.config/user-dirs.dirs` (parsed via `grep`/`string match`, not - sourced as shell), per the parent spec's decision that this file is the - single source of truth. This machine's real `user-dirs.dirs` was - deliberately left untouched/untracked and no live migration was run against - this machine's actual home directory — the user chose "code + tests only" - scope for this task (a real rename of `~/Desktop`, `~/Documents`, etc. is a - separate, explicit action to take later), so only the scratch-`$HOME` - fishtape fixtures exercise the short-name `user-dirs.dirs` content. - Tracking the real file and running the real migration remains open. -- During `/review-uncommitted`, the spec-fidelity pass caught a real bug: the - nested `Pictures/Screenshots→pic/screenshots` move ran unconditionally, - before checking whether `Pictures` held other, unrelated content — so a - `Pictures` folder with both `Screenshots/` and some other file got - partially mutated (Screenshots pulled out) while still being reported as - "left in place." Fixed by gating the Screenshots move on the rest of the - folder being empty too; added a regression test for this case - ("Screenshots is not peeled off... when Pictures still has other - content"). -- Completions (`~/.config/fish/completions/dot.fish`) got a `dot setup` - block mirroring `dot kde`'s per-subcommand completion entries, even though - the task's required "completions/help-glob duplication point" is already - satisfied automatically by the existing generic directory glob (no changes - were needed there for `dot setup`/`dot help` to discover the new nested - command). The added completions are a small polish addition beyond the - strict letter of the acceptance criteria, consistent with the existing - `kde` subcommand's treatment. diff --git a/.config/dot/.claude/tasks/0007-folders-non-empty-confirmation.md b/.config/dot/.claude/tasks/0007-folders-non-empty-confirmation.md deleted file mode 100644 index 76f1b6f..0000000 --- a/.config/dot/.claude/tasks/0007-folders-non-empty-confirmation.md +++ /dev/null @@ -1,52 +0,0 @@ ---- -spec: dot-setup-folders -blocked-by: 0006-setup-dispatcher-and-folders-core ---- - -## What to build - -Extend `dot setup folders`'s migration so a legacy folder found non-empty -(any entry at all, including a stray dotfile or KDE metadata like a -`.directory` file, counts as non-empty) stops and prints what would be moved, -then refuses to proceed unless an explicit `--yes` flag was passed on the -command line — no interactive prompt. With `--yes`, the migration proceeds -for that folder the same way the empty-folder path already does. - -This applies uniformly across all 8 mapped categories, including the nested -`Pictures/Screenshots→pic/screenshots` rename from the prior slice: a -non-empty `Screenshots` folder is also gated behind the same confirmation -rule. - -## Acceptance criteria - -- [x] A legacy folder with real content (a real file, not just an empty - directory) refuses to migrate without `--yes`, prints what would have - been moved, and leaves the folder and its contents untouched -- [x] The same legacy folder migrates successfully when `--yes` is passed -- [x] A legacy folder containing only a stray dotfile/metadata file (e.g. a - fake `.directory`) is still treated as non-empty and triggers the same - confirmation gate -- [x] `~/.config/dot/tests/dot.fish` covers the above cases and - `fishtape ~/.config/dot/tests/dot.fish` passes - -## Implementation Notes - -- `--yes`'s actual move reuses the exact same branch shape as the existing - silent-empty path (rename `Screenshots` → `screenshots` when present, then - `rmdir` the legacy folder), extended to also `mv` any remaining top-level - entries into the target first. Screenshots is always moved as one atomic - unit — its individual files are never mv'd/reported separately — so a - non-empty `Screenshots` (own acceptance criterion in the parent spec) is - gated and migrated the same way a non-empty top-level file would be. -- Collision handling (no-clobber `mv -n`, reporting skipped files, leaving the - legacy folder in place on a collision) is explicitly out of scope here — - it's owned by 0008-folders-collision-handling.md, per that task's own - frontmatter/spec section. The `--yes` path added here uses a plain `mv`. -- `/review-uncommitted` flagged two minor issues, both fixed: a stale comment - claiming a helper variable was used by both the silent-empty and `--yes` - paths when it was only read by the latter, and a duplicated `find` - invocation computing the same top-level listing twice under one condition - (now computed once and reused). It also flagged the non-empty "would move" - preview listing recursively-nested files individually instead of treating - `Screenshots` as one unit like the real move does — fixed so the preview - and the actual move share the same top-level-entries list. diff --git a/.config/dot/.claude/tasks/0008-folders-collision-handling.md b/.config/dot/.claude/tasks/0008-folders-collision-handling.md deleted file mode 100644 index 9e29b16..0000000 --- a/.config/dot/.claude/tasks/0008-folders-collision-handling.md +++ /dev/null @@ -1,55 +0,0 @@ ---- -spec: dot-setup-folders -blocked-by: 0007-folders-non-empty-confirmation ---- - -## What to build - -Make the `--yes`-confirmed merge from the prior slice collision-safe: when a -legacy folder and its short-named target both contain an entry with the same -name, use no-clobber move semantics so the target's existing file is never -silently overwritten. Report which files were skipped due to a collision, and -leave the legacy folder in place (don't remove it) whenever any collision -occurred during that folder's migration, rather than deleting a folder that -still holds something that couldn't be merged. - -This closes the gap left by the old bash `setup_folders`'s naive `mv $from/* -$to`, which had no collision protection at all. - -## Acceptance criteria - -- [x] A filename collision between a legacy folder and its already-populated - short-named target is skipped, not overwritten (the target's existing - file is preserved byte-for-byte) -- [x] The skipped collision is reported to the user -- [x] The legacy folder is left in place (not removed) when a collision - occurred, even though `--yes` was given and other non-colliding files - in it were moved -- [x] Re-running `dot setup folders` after a collision was reported and left - in place behaves consistently (doesn't lose the previously-skipped - file, doesn't re-move already-migrated files) -- [x] `~/.config/dot/tests/dot.fish` covers the above cases and - `fishtape ~/.config/dot/tests/dot.fish` passes - -## Implementation Notes - -- The two prior branches (silent-empty merge vs. `--yes`-confirmed merge) - were unified into one `if test (count $other_entries) -eq 0; or set -q - _flag_yes` branch, since the collision-detection/no-clobber logic is - identical either way. This has one side effect beyond the letter of the - acceptance criteria (which frame collision handling around the `--yes` - path): a legacy folder that's otherwise "empty" except for an emptyish - nested `Screenshots` dir now also gets collision-checked against an - already-populated `pic/screenshots` on the silent, no-`--yes` path. This - closes the same unguarded-`mv` gap the spec calls out as the motivating - problem (the old code's silent-path `mv $screenshots_path - $target_path/screenshots` had no collision protection at all either), so - it was kept rather than special-cased away. Covered by its own test - ("a silent-path Screenshots collision ..."). -- Collision detection is a pre-check (`test -e $target_path/...`) before an - actual `mv -n`, rather than relying on `mv -n`'s exit code alone, so each - colliding entry can be individually identified and reported by path. -- `/review-uncommitted` (risk: Medium, standards: 0 hard violations, spec: - 0 missing/wrong requirements) raised no changes needed; the one scope note - it flagged (the silent-path Screenshots case above) was a deliberate, - judged-correct decision rather than an oversight. diff --git a/.config/dot/.claude/tasks/0009-kde-shortcut-completion.md b/.config/dot/.claude/tasks/0009-kde-shortcut-completion.md deleted file mode 100644 index ed59789..0000000 --- a/.config/dot/.claude/tasks/0009-kde-shortcut-completion.md +++ /dev/null @@ -1,51 +0,0 @@ ---- -blocked-by: 0005-kde-shortcuts-mechanism ---- - -## What to build - -`dot kde save`'s tab-completion (`cmd_complete` in `commands/kde/kde.py`) -currently only enumerates schema-backed identifiers via -`iter_schema_identifiers` — it was built as a side effect of the `diff` -task (0003) and never revisited when the shortcuts mechanism (0005) -landed. Extend `cmd_complete` to also enumerate shortcut identifiers. - -Source the shortcut identifiers live via `kglobalaccel`, mirroring how -schema identifiers are freshly parsed from `.kcfg` files on every call: -call `allMainComponents()` to get every registered component's -`componentUnique`, then `allActionsForComponent()` per component -(already used by `_resolve_shortcut_action_id`) to get every -`actionUnique`, yielding `kglobalshortcutsrc..` -candidates. No caching — walk fresh on every invocation. - -Print shortcut identifiers as their own block, after the existing -schema-backed block — not merged into one interleaved sorted list. -Keep them plain, with no friendly-name description text, matching the -existing schema-identifier output style. - -If the D-Bus walk fails for any reason — a non-zero `busctl` exit -(`RuntimeError`, already raised by `_kglobalaccel_call`) or `busctl` -itself being missing (`OSError` from `subprocess.run`) — swallow it -silently: omit the shortcuts block, still print the schema block, and -emit no stderr diagnostic. - -Freeform identifiers (e.g. `kxkbrc.Layout.Options`) are explicitly out -of scope for this task: there is no schema to enumerate them from, so -this stays a permanent, accepted completion gap, not something to fix -here. - -## Acceptance criteria - -- [x] `python3 kde.py complete` includes every currently-registered `kglobalshortcutsrc..` identifier, sourced live via `allMainComponents`/`allActionsForComponent` -- [x] Schema-backed identifiers print first, followed by shortcut identifiers, as two distinct blocks — not interleaved into one merged sorted list -- [x] Shortcut identifiers print plain, with no friendly-name description text -- [x] If the D-Bus walk raises `RuntimeError` or `OSError`, the shortcuts block is omitted, the schema block still prints normally, and nothing is written to stderr -- [x] Freeform identifiers remain unlisted by `cmd_complete` (unchanged, confirmed not a regression) -- [x] Verified manually against a live session — no new automated tests, consistent with the existing shortcuts-mechanism test carve-out (spec's testing decisions, 0005's Implementation Notes) - -## Implementation Notes - -- `iter_shortcut_identifiers` (new, `commands/kde/kde.py`) walks `allMainComponents()` then `allActionsForComponent()` per component, yielding `kglobalshortcutsrc..`. `cmd_complete` wraps that walk in `sorted(set(...))` and appends it as a second print loop after the existing schema-backed one, inside a `try/except (RuntimeError, OSError)` that falls back to an empty list on any failure — so a missing `busctl` or an unreachable D-Bus session degrades completion instead of breaking it. -- Manually verified both paths: live run on this machine prints 278 shortcut identifiers after 322 schema-backed ones; with `busctl` removed from `PATH` (simulating a non-KDE/minimal shell), `cmd_complete` still exits 0, prints only the 322 schema identifiers, and writes nothing to stderr. -- `/review-uncommitted`'s Standards pass flagged two judgement-call smells: (1) the D-Bus call/unpack idiom for `allActionsForComponent` was duplicated between the new function and `_resolve_shortcut_action_id`; (2) the silent `except` swallow had no comment explaining why. Fixed both: extracted a shared `_actions_for_component(component_unique)` helper used by both call sites, and added a comment on the `try` explaining that fish invokes this on every TAB press in shells that may lack a live KDE session, so a broken shortcuts source must never cost the already-printed schema candidates. Re-ran the full test suite (101/101 pass) and both manual checks after the fix. -- No automated tests added, per the task's own acceptance criterion and the shortcuts mechanism's existing test carve-out (0005's Implementation Notes: a live D-Bus session isn't practically substitutable without disproportionate mock infrastructure). diff --git a/.config/dot/.claude/tasks/0010-kde-shortcuts-diff-broadscan.md b/.config/dot/.claude/tasks/0010-kde-shortcuts-diff-broadscan.md deleted file mode 100644 index 0d1ca7d..0000000 --- a/.config/dot/.claude/tasks/0010-kde-shortcuts-diff-broadscan.md +++ /dev/null @@ -1,102 +0,0 @@ ---- -blocked-by: [0005-kde-shortcuts-mechanism, 0009-kde-shortcut-completion] ---- - -## What to build - -`dot kde diff`'s broad-scan (the pass that reports *undeclared* drift, not -just already-declared entries) currently only walks schema-backed -identifiers via `iter_schema_identifiers`. Shortcuts are treated the same -as freeform in `cmd_diff` -- checked only when already present in the -manifest -- per the code comment at the top of that loop. That comment is -overstated for shortcuts: unlike freeform, which genuinely has no -enumeration source, shortcuts *are* enumerable via `kglobalaccel`'s -`allMainComponents`/`allActionsForComponent`, and `iter_shortcut_identifiers` -(added in 0009 for tab-completion) already walks exactly that. - -Add a second broad-scan pass in `cmd_diff`, after the existing schema-backed -one, over `sorted(set(iter_shortcut_identifiers()))`: for each identifier, -compare `shortcutKeys` against `defaultShortcutKeys` (the same live/default -read already used for declared shortcuts), and tag `declared`/`undeclared` -exactly like the schema loop. Remove the shortcuts branch from the -manifest-only loop below it (now redundant), leaving that loop for freeform -only, since freeform is the only mechanism that still can't be enumerated. - -Tolerate two failure modes without aborting the whole command: -- The enumeration call itself (`allMainComponents`) failing (no live - session, no `busctl`) -- print one diagnostic to stderr and skip the - shortcuts block entirely, same as any other reported problem in `diff`. -- An individual action failing to resolve (`_resolve_shortcut_action_id` - raising because its owning app hasn't registered with kglobalaccel this - session) -- print that one identifier's error to stderr and continue, - matching the schema loop's existing per-identifier tolerance. - -Update `DIFF_USAGE` to reflect that shortcuts now participate in broad-scan -alongside schema-backed settings, leaving only freeform as declared-only. - -## Acceptance criteria - -- [x] `dot kde diff` reports undeclared shortcut drift (a shortcut changed - from its packaged default but never `dot kde save`d) without requiring - it to be in the manifest first -- [x] Already-declared shortcut drift is still reported, tagged `declared`, - with no duplicate line from the old manifest-only loop -- [x] A shortcut belonging to an app that hasn't registered with kglobalaccel - this session produces one stderr diagnostic for that identifier and - does not stop the rest of the scan (schema block, other shortcuts, - freeform block) from completing -- [x] If the `allMainComponents` enumeration itself fails (no `busctl`, no - live session), `diff` prints one diagnostic, skips the shortcuts block, - and still completes the schema and freeform passes, exiting 0 -- [x] Freeform remains declared-only (unchanged) -- only its loop comment and - the removed shortcuts branch change -- [x] `DIFF_USAGE` text updated to describe shortcuts as broad-scanned -- [x] Verified manually against the real session (consistent with the - shortcuts mechanism's existing test carve-out, 0005/0009) -- no new - automated tests -- [x] Full existing test suite still passes unchanged - -## Implementation Notes - -- `cmd_diff` (`commands/kde/kde.py`) gained a second broad-scan pass between - the existing schema-backed loop and the manifest-only loop: it walks - `sorted(set(iter_shortcut_identifiers()))` (the same enumeration - `cmd_complete` already uses), compares `shortcutKeys` against - `defaultShortcutKeys` per identifier, and tags `declared`/`undeclared` - exactly like the schema loop. -- The manifest-only loop below it lost its `shortcuts` branch entirely - (`resolve_mechanism` returning `"shortcuts"` now just falls through - `if mechanism != "freeform": continue`), since the new broad-scan pass - already reports every declared shortcut mismatch -- keeping the old branch - would have double-printed them. -- Two failure modes, handled at different granularity: `iter_shortcut_identifiers()` - itself is wrapped in `try/except (RuntimeError, OSError)` -- a failure there - (no live session, missing `busctl`) prints one diagnostic and skips the - whole shortcuts block, letting the schema and freeform passes still run. - Inside the per-identifier loop, `read_shortcut_value` raising `RuntimeError` - (an app that hasn't registered with kglobalaccel this session yet) prints - one diagnostic for that identifier and continues, matching the schema - loop's existing per-identifier tolerance. -- Real-world validation on this machine: manually ran the same enumeration in - a throwaway script before implementing, confirming 29 of 278 registered - shortcuts differed from default (the Meta+1-9 desktop-switch remap, - Meta+Shift+1-9 window-to-desktop binds, and Meta+A/Meta+Shift+A activity - switching) -- all 29 were `dot kde save`d into the manifest in the same - session as a prerequisite for testing this cleanly. After implementing, - `dot kde diff` reported all 30 shortcuts (29 plus the pre-existing - `ksmserver.Lock Session`) as `declared` with correct default values, and - ~34 unrelated `RuntimeError`s for apps not launched this session (Konsole, - Spectacle, Dolphin, etc.) printed to stderr without aborting the scan. - Removing one entry (`kwin.Switch to Desktop 1`) from the manifest and - re-running confirmed it flips to `undeclared` with the same live/default - values, then restoring the manifest flipped it back to `declared` -- - confirms both tags work and the manifest was left untouched by `diff` - itself (read-only, as documented). -- Full test suite re-run after the change: 101/101 pass, unchanged from - before this task. No automated tests added for the new pass itself, per - the shortcuts mechanism's existing carve-out (0005's Implementation Notes: - a live `kglobalaccel` D-Bus session isn't practically substitutable without - disproportionate mock infrastructure) -- the existing tests already - exercise `dot kde diff` against the real live session and continued to - pass with the new pass active, incidentally covering that it doesn't break - anything even though it isn't asserting on the new pass's own output. diff --git a/.config/dot/.claude/tasks/0011-folders-unconditional-merge.md b/.config/dot/.claude/tasks/0011-folders-unconditional-merge.md deleted file mode 100644 index 9084ff3..0000000 --- a/.config/dot/.claude/tasks/0011-folders-unconditional-merge.md +++ /dev/null @@ -1,79 +0,0 @@ ---- -spec: dot-setup-folders -blocked-by: 0008-folders-collision-handling ---- - -## What to build - -Remove the `--yes` confirmation gate that 0007/0008 built: a legacy folder -with real content in it is migrated unconditionally now, the same as an -empty one, since the collision handling from 0008 already makes the merge -non-destructive on its own (a same-named entry is never overwritten, and the -legacy folder is kept whenever any collision occurred). The `--yes` gate -turned out to protect against a scenario collision handling already -prevents, while making the everyday case — a machine that already has real -files in `~/Documents`, `~/Pictures`, etc. — a silent no-op unless the flag -was remembered, which defeats the point of the task. - -In its place: - -- `dot setup folders` always attempts the merge for every legacy folder, - content or none. -- A new `--dry-run` flag replaces `--yes` in the flag slot: it reports what - would move and what would be skipped as a collision, without touching the - filesystem at all (no `mkdir`, no `mv`/`rmdir`, no `xdg-user-dirs-update`). -- A real (non-dry-run) run now reports what it moved per legacy folder - (e.g. `moved 12 entries from ~/Documents to ~/doc`), instead of staying - silent on success. A folder where nothing top-level moved (already empty, - or everything in it collided) prints no such line — only non-trivial moves - and collisions produce output. -- `--yes` is removed outright (not kept as a silent no-op): passing it now - fails with argparse's standard unknown-option error. - -## Acceptance criteria - -- [x] A legacy folder with real content merges on a plain `dot setup - folders`, with no flag required -- [x] A real run prints `moved N entries from ~/ to ~/` for a - folder where top-level entries actually moved, and nothing for a - folder where none did -- [x] A real run prints a dedicated line when the nested Screenshots folder - itself is moved (e.g. `moved ~/Pictures/Screenshots to ~/pic/screenshots`) -- [x] Collision detection/reporting and the "leave the legacy folder in - place when a collision occurred" behavior from 0008 are unchanged - under the new unconditional default -- [x] `--dry-run` reports the same would-move/would-skip information without - creating any target directory, moving/removing anything, or invoking - `xdg-user-dirs-update` -- [x] `dot setup folders --yes` fails with an unknown-option error (argparse - default), rather than being silently accepted or gated on -- [x] `dot setup folders help` output no longer mentions `--yes` and - documents `--dry-run` instead -- [x] Idempotency holds: re-running after a clean merge, and re-running - after a collision was reported, both behave the same as before -- [x] `~/.config/dot/tests/dot.fish` is updated to exercise the above - (replacing the old `--yes`-gated cases) and - `fishtape ~/.config/dot/tests/dot.fish` passes - -## Implementation Notes - -- The `--yes` gate and the `screenshots_emptyish`/`other_entries` machinery - that computed it were deleted outright rather than special-cased away: - once merging is unconditional, that machinery had no remaining purpose - (it existed solely to decide "empty enough to skip the gate"). -- `mkdir -p $target_path` and the final `xdg-user-dirs-update` are both now - guarded by `not set -q _flag_dry_run`, making `--dry-run` a true no-op - rather than "no-op except for directory scaffolding." -- Collision detection (`test -e $target_path/...`) runs identically in both - modes; `--dry-run` only gates the actual `mv`/`rmdir`/`mkdir` calls, so the - reported would-move/would-skip split is exactly what a real run would do. -- Success reporting is per-legacy-folder and suppressed at zero: a folder - that was already empty (or whose only entries all collided) prints - nothing, so a routine re-run stays quiet like before. -- All prior collision/idempotency/Screenshots test scenarios were kept, - just re-pointed at the plain `dot setup folders` invocation instead of - `--yes`; two scenarios that only differed by which code branch (`--yes` - vs. silent-empty) they exercised now hit the same branch, but were both - kept since they still cover distinct fixture shapes (Pictures with vs. - without unrelated top-level content alongside a colliding Screenshots). -- `fishtape ~/.config/dot/tests/dot.fish` passes (178 tests). diff --git a/.config/dot/.claude/tasks/0012-folders-fixed-target-mapping.md b/.config/dot/.claude/tasks/0012-folders-fixed-target-mapping.md deleted file mode 100644 index f4cf818..0000000 --- a/.config/dot/.claude/tasks/0012-folders-fixed-target-mapping.md +++ /dev/null @@ -1,64 +0,0 @@ ---- -spec: dot-setup-folders -blocked-by: 0011-folders-unconditional-merge ---- - -## What to build - -Stop reading the short-name target from `~/.config/user-dirs.dirs` and -hardcode the legacy-name -> short-name mapping directly in -`_dot_setup_folders`, dropping the dependency on that file entirely. - -The original design treated the tracked `user-dirs.dirs` as the single -source of truth for target names, assuming someone would hand-edit it to -the short names before ever running the command. On this machine that -never happened: the tracked file still had the stock XDG defaults -(`XDG_DOCUMENTS_DIR="$HOME/Documents"`, etc.), so `target_path` resolved to -the exact same directory as `legacy_path` for every folder. The migration -logic then reported every entry as a "collision" against itself instead of -moving anything -- a confusing, silent-feeling failure rather than an -actual migration. - -The short names are fixed (`.desktop`, `doc`, `dwn`, `mus`, `pic`, `vid`, -`.ignoreme`) and not meant to be configurable, so there's nothing to read -from a file in the first place. `user-dirs.dirs` remains a separate, -manually tracked dotfile (edited and tracked by hand, like any other -dotfile) for apps/`xdg-user-dirs-update` to consult -- `dot setup folders` -itself no longer reads it, requires its presence, or writes to it. - -## Acceptance criteria - -- [x] `_dot_setup_folders` no longer reads, parses, or requires - `~/.config/user-dirs.dirs`; the legacy->short-name mapping is a fixed - table in the function itself -- [x] Migration works identically whether `user-dirs.dirs` is absent, - empty, or declares stale/full-name values (the exact real-world case) -- [x] `user-dirs.dirs` is left byte-for-byte untouched by `dot setup - folders` when present, and no file is created when absent -- [x] `dot setup folders help` no longer describes reading target names - from `user-dirs.dirs` -- [x] `~/.config/dot/tests/dot.fish` no longer seeds a `user-dirs.dirs` - fixture as a migration precondition, and covers the stale/missing - cases above; `fishtape ~/.config/dot/tests/dot.fish` passes - -## Implementation Notes - -- Replaced the `xdg_vars`/`grep`/`string match` parsing of `user-dirs.dirs` - with two parallel hardcoded arrays, `legacy_names` and `target_names`, - indexed together -- same shape the code already used for `legacy_names` - alone, just extended to cover the target side too. -- The early `if not test -f $user_dirs; return 1` guard was deleted outright - rather than kept as a soft check: there's nothing left for the function to - read from that file, so requiring its existence would just be a - vestigial, unjustifiable precondition. -- Removed the `short_name_user_dirs` fixture and its seeding step from every - test scenario (it was previously duplicated into ~15 scenarios as a - migration precondition); added two new scenarios instead: one reproducing - the exact real-machine bug (stale full-name `user-dirs.dirs` values) and - one confirming migration works with no `user-dirs.dirs` file at all. -- Verified against this machine's real, still-stale `~/.config/user-dirs.dirs` - via `_dot_setup_folders --dry-run`: previously reported every entry in - Desktop/Documents/Downloads/Pictures/Videos as a collision against - itself; now correctly reports `would move N entries from ~/Documents to - ~/doc` etc. -- `fishtape ~/.config/dot/tests/dot.fish` passes (183 tests). diff --git a/.config/dot/CLAUDE.md b/.config/dot/CLAUDE.md deleted file mode 100644 index 74d6bc3..0000000 --- a/.config/dot/CLAUDE.md +++ /dev/null @@ -1,167 +0,0 @@ -# Dotfiles - -This machine's dotfiles are a bare git repo at `~/.dotfiles`, checked out with -`$HOME` as its work-tree. The `dot` fish function wraps that invocation -(`git --git-dir=~/.dotfiles --work-tree=$HOME $argv`, declared with -`--wraps=git`), so every git subcommand works through it: `dot status`, -`dot add`, `dot commit`, `dot push`, etc. - -This directory (`~/.config/dot`) holds the `dot` CLI's custom subcommands, -tests, and package lists, but the repo tracks files across `$HOME` — fish -config, git identity, the `dot` function itself, and more. To see everything -tracked, run `dot ls-tree -r --name-only HEAD` from `$HOME` (paths are shown -relative to cwd, so running it from elsewhere silently truncates the list). - -For an agent driving this through separate tool calls: `cd ~` in one call does -not reliably carry over to the next, since each call may reset to the -project's working directory. Always `cd "$HOME"` and run the `ls-tree` (or any -other cwd-sensitive `dot`/`git` command) in that *same* call — e.g. -`cd "$HOME" && dot ls-tree -r --name-only HEAD` — rather than trusting a prior -`cd` to have stuck. Getting this wrong silently narrows the listing to -whatever the leftover cwd happens to be, which reads as "this file isn't -tracked" when it actually is. - -## Always add by explicit path - -`status.showUntrackedFiles=no` is set locally (see `dot init` below), and -`.gitignore` only excludes `.dotfiles` itself plus OS/editor cruft — it is -**not** a whitelist. That -means virtually everything under `$HOME` reads as untracked, and `git status` -deliberately hides all of it. - -**Always run `dot add `.** Never `dot add -A`, `dot add .`, or -any wildcard add — that would try to stage the entire home directory (caches, -secrets, everything). - -**Stage automatically after changes.** Once a tracked file is edited, run -`dot add ` for it right away rather than waiting to be asked — -one explicit path per changed file, still never a wildcard. This does not -extend to `dot commit` or `dot push`, which still require an explicit -request. - -## The dot CLI - -### Architecture - -`dot` is defined in one file: `~/.config/fish/functions/dot.fish`. It holds -three functions: - -- `dot` (`--wraps=git`) — dispatches `init`, `help`, and any file found under - `~/.config/dot/commands/`, otherwise forwards everything to - `git --git-dir=~/.dotfiles --work-tree=$HOME $argv` (full passthrough). -- `__dot_init` — the bootstrap logic, inlined in the same file rather than - autoloaded separately, because it's the one subcommand that must work - before the dotfiles repo has ever been cloned onto a machine. -- `__dot_help` — prints usage: the built-in commands plus whatever is - currently found under `~/.config/dot/commands/`, generated by globbing that - directory rather than a hardcoded list, so it can't drift from reality. - -`__dot_help`'s glob over `~/.config/dot/commands/*.fish` is duplicated in -`~/.config/fish/completions/dot.fish`'s `__dot_custom_subcommands` rather than -shared: fish only autoloads a function from a file named after that function, -so a helper defined inside `dot.fish` would be undefined if tab-completion -ran before `dot` had ever been sourced in the session. Keep both copies in -sync when the listing logic changes. -Both copies also glob one directory level deeper, matching `~/.config/dot/commands//.fish`, so a subcommand's companion file (e.g. a Python helper) can live alongside it in its own directory. - -`dot init`: - -- refuses to run if `~/.dotfiles` already exists (no re-init support) -- clones the bare repo from `--url` (default: the hardcoded Gitea remote) — - if the clone fails, it errors out; it never falls back to `git init` -- backs up any pre-existing file that checkout would clobber into - `~/.dotfiles-backup//`, then retries the checkout -- explicitly sets `status.showUntrackedFiles=no` after cloning — this is a - local-only git setting, so a fresh `git clone` never carries it over - -### Adding a subcommand - -Beyond `init`, `dot` looks for `~/.config/dot/commands/.fish`, sources -it, and calls `_dot_`. -A subcommand needing a companion file can instead live nested one level deeper, as `~/.config/dot/commands//.fish` — both layouts dispatch identically. -These files are deliberately kept out of -`~/.config/fish/functions/` (fish's autoload path) so they never become -independently invokable top-level commands or clutter tab-completion outside -of `dot` itself. - -1. Create `~/.config/dot/commands/.fish` defining a `_dot_` - function. -2. Confirm `dot ` dispatches to it. No other wiring is needed — - `~/.config/fish/completions/dot.fish` and `__dot_help` both discover new - command files by globbing that directory, and `--wraps=git` still covers - raw git subcommands. -3. Implement a `help` subcommand: check for `help` as `_dot_`'s first - positional argument before `argparse`, and call a `_dot__usage` - function that prints usage and every flag. If `_dot_` itself - dispatches to nested subcommands, apply this same check-then-dispatch - pattern at that level too — there's no central `--help` handling in - `dot.fish` to lean on; each level is responsible for its own. - `_dot__usage` should print its text as a single multi-line - `echo "..."` string (fish preserves literal newlines inside double - quotes) rather than one `echo` per line. -4. Add a row to `~/.github/README.md`'s command table for it — one row per - distinct use case, with paths written relative to `$HOME` - (`~/.config/dot/...`), not relative to the README's own location. -5. Add a case to `~/.config/dot/tests/dot.fish` covering it, including its - `help` output, and run `fishtape ~/.config/dot/tests/dot.fish` until it - passes. - -### Testing - -Tests live at `~/.config/dot/tests/dot.fish`, run with -`fishtape ~/.config/dot/tests/dot.fish`. Fishtape is installed via Fisher -(`fisher install jorgebucaran/fishtape`) and tracked in -`~/.config/fish/fish_plugins` — a real, restorable dependency for developing -`dot`, but never required just to use it. - -- Each scenario overrides `$HOME` (`set -gx HOME (mktemp -d)`) before calling - `dot`, so tests never touch the real `~/.dotfiles`. -- Build a throwaway bare "remote" fixture with `git init --bare` plus a - seeded commit, and explicitly set its `HEAD` - (`git --git-dir=$remote symbolic-ref HEAD refs/heads/main`). Pushing with - `git push origin HEAD:main` does **not** update the bare repo's `HEAD` - symref — skip this and a clone of the fixture can end up "on a branch yet - to be born." -- Don't use `.gitconfig` as a fake pre-existing "conflict" file in a - fixture — git parses `$HOME/.gitconfig` as its own global config on every - invocation, and garbage content there spams "key does not contain a - section" errors that drown out the real assertion. Use a harmless file - like `.bashrc` instead. -- `@test "description" ` mirrors fish's `test` builtin - (`-eq`, `-ne`, `=`, `-e`, `-f`, `-d`, `-n`, `-z`); `-a`/`-o` combinators - aren't supported. - -## Gotchas - -- `~/.claude/` (Claude Code's own config: skills, agents, commands, etc.) is - a plain directory, not a separate git repo of its own — plain `git` commands - run from inside it report "not a git repository". It's tracked the same way - as everything else under `$HOME`: through the `dot` bare repo. Use - `dot add`/`dot status` on paths under `~/.claude/`, not a `git` invocation - scoped to that directory, and don't assume an unrelated repo (e.g. a - separate skills-source checkout elsewhere) is the tracked copy just because - it also holds a copy of the same files. -- `~/.claude/` and this project's own `.claude/` (e.g. `~/.config/dot/.claude/`) - are two different directories that both happen to exist. Project-relative - paths referenced in specs, task breakdowns, or other project docs — like - `.claude/spec/.md` or `.claude/tasks/-.md` — are relative - to this project directory (`~/.config/dot/.claude/...`), not to - `$HOME/.claude/`. Writing to `$HOME/.claude/tasks/` instead of - `~/.config/dot/.claude/tasks/` silently lands files in Claude Code's own - global config dir instead of the project. -- An agent's `Bash` tool runs commands through **zsh**, not fish, so the - `dot` fish function (defined in `~/.config/fish/functions/dot.fish`) is - not on that shell's autoload path. Typing `dot ` there - silently resolves to `/usr/bin/dot` (Graphviz) instead, producing - confusing "can't open : No such file or directory" / "syntax error - near '--'" errors rather than a clear "command not found". Either invoke - it as `fish -c "dot ..."`, or bypass the wrapper and call - `git --git-dir=$HOME/.dotfiles --work-tree=$HOME ` directly. - -## Keybindings - -Whenever a keybind is added, changed, or removed in *any* config on this -machine (tmux, KDE, neovim, fish, whatever), add or update its row in -[`~/.github/keybindings.md`](../../.github/keybindings.md) in the same -change. That file is the single reference for every keybind across tools — -it drifts the moment a bind changes somewhere without a matching edit there. diff --git a/.config/dot/commands/install.fish b/.config/dot/commands/install.fish deleted file mode 100644 index e87eadc..0000000 --- a/.config/dot/commands/install.fish +++ /dev/null @@ -1,59 +0,0 @@ -function _dot_install_usage - echo "usage: dot install [--restore] [--no-sync] [package ...] - --restore reinstall every package from the tracked list - --no-sync skip 'pacman -Sy' before installing" -end - -function _dot_install - if test "$argv[1]" = help - _dot_install_usage - return 0 - end - - argparse 'restore' 'no-sync' -- $argv - or return 1 - - set -l list_dir $HOME/.config/dot/packages - set -l list_file $list_dir/pacman - set -l packages - - if set -q _flag_restore - if test (count $argv) -gt 0 - echo "dot install: --restore cannot be combined with package names" >&2 - return 1 - end - - if not test -s $list_file - echo "dot install: no package list found at $list_file" >&2 - return 1 - end - - set packages (cat $list_file) - else - if test (count $argv) -eq 0 - echo "dot install: no packages given (use --restore to reinstall from the list)" >&2 - return 1 - end - - set packages $argv - end - - if not set -q _flag_no_sync - sudo pacman -Sy - or return 1 - end - - sudo pacman -S --needed $packages - or return 1 - - if set -q _flag_restore - return 0 - end - - mkdir -p $list_dir - test -f $list_file - or touch $list_file - - printf '%s\n' $packages >>$list_file - sort -u -o $list_file $list_file -end diff --git a/.config/dot/commands/kde/kde.fish b/.config/dot/commands/kde/kde.fish deleted file mode 100644 index 6157d8e..0000000 --- a/.config/dot/commands/kde/kde.fish +++ /dev/null @@ -1,35 +0,0 @@ -function _dot_kde_usage - echo "usage: dot kde - -Commands: - apply push manifest entries onto the live system - diff scan for settings whose live value differs from its default - save write live KDE settings into the manifest - help show this message - -Run 'dot kde help' for flags on a specific command." -end - -function _dot_kde - if test "$argv[1]" = help - _dot_kde_usage - return 0 - end - - set -l helper_dir (status dirname) - - switch "$argv[1]" - case apply - python3 $helper_dir/kde.py apply $argv[2..-1] - return $status - case diff - python3 $helper_dir/kde.py diff $argv[2..-1] - return $status - case save - python3 $helper_dir/kde.py save $argv[2..-1] - return $status - case '*' - _dot_kde_usage - return 1 - end -end diff --git a/.config/dot/commands/kde/kde.py b/.config/dot/commands/kde/kde.py deleted file mode 100644 index 4b0effc..0000000 --- a/.config/dot/commands/kde/kde.py +++ /dev/null @@ -1,535 +0,0 @@ -#!/usr/bin/env python3 -import json -import os -import subprocess -import sys -import xml.etree.ElementTree as ET -from collections import defaultdict, namedtuple -from pathlib import Path - -KCFG_NS = "{http://www.kde.org/standards/kcfg/1.0}" -DEFAULT_SCHEMA_DIR = "/usr/share/config.kcfg" - -KGLOBALACCEL_SERVICE = "org.kde.kglobalaccel" -KGLOBALACCEL_PATH = "/kglobalaccel" -KGLOBALACCEL_IFACE = "org.kde.KGlobalAccel" -# KGlobalAccel::GlobalShortcutLoading::NoAutoloading, per KF6/KGlobalAccel/kglobalaccel.h -- -# makes a write always win over whatever shortcut was previously saved, rather than being -# ignored in favor of it (the Autoloading=0x0 default). -SHORTCUT_NO_AUTOLOADING = 0x4 - -# .kcfg files that only declare their target rc file at runtime -# (), so it can't be discovered by scanning. -ARG_TRUE_RCFILES = { - "kwin.kcfg": "kwinrc", -} - -SAVE_USAGE = """usage: dot kde save [identifier] - - identifier declare a new manifest entry, seeded from its current live value - (no args) refresh every already-declared manifest entry from the live system - help show this message""" - -APPLY_USAGE = """usage: dot kde apply - - Pushes every manifest entry's declared value onto the live system. - help show this message""" - -DIFF_USAGE = """usage: dot kde diff - - Scans every schema-backed setting reachable through the kcfg mapping - table, and every shortcut registered with kglobalaccel, reporting each - one whose live value differs from its default, tagged declared - (present in the manifest) or undeclared. Also reports already-declared - freeform settings whose live value differs from their default (no - schema to broad-scan, so it's only checked when already declared). - Read-only -- never writes the manifest or the live system. - help show this message""" - -Setting = namedtuple("Setting", ["file", "group", "key"]) - - -def _split_on_known_prefix(rest, candidates): - matches = [c for c in candidates if rest == c or rest.startswith(c + ".")] - if not matches: - return None - - prefix = max(matches, key=len) - remainder = rest[len(prefix):].lstrip(".") - if not remainder: - return None - return prefix, remainder - - -def _known_schema_groups(file, kcfg_map): - groups = set() - for path in kcfg_map.get(file, []): - root = _parse_kcfg(path) - if root is None: - continue - for group_elem in root.iter(f"{KCFG_NS}group"): - name = group_elem.get("name") - if name: - groups.add(name) - return groups - - -def _split_schema_group_key(file, rest, kcfg_map): - match = _split_on_known_prefix(rest, _known_schema_groups(file, kcfg_map)) - if match is not None: - return match - - # No schema group matches -- freeform. Its group is never known to contain - # a dot (there's no schema to have told us otherwise), so the boundary is - # just the first remaining dot. - group, _, key = rest.partition(".") - if not key: - raise ValueError(f"invalid identifier {file}.{rest!r} (expected file.group.key)") - return group, key - - -def _split_shortcut_group_key(rest): - (components,) = _kglobalaccel_call("allMainComponents", None) - match = _split_on_known_prefix(rest, [component[0] for component in components]) - if match is None: - raise RuntimeError( - f"no live kglobalaccel component matches {rest!r} " - "(the owning application may need to run once to register its shortcuts with kglobalaccel)" - ) - return match - - -# Only the file segment is unambiguous (rc file names never contain a dot). -# The group/key boundary can't be found by counting dots -- both KConfig group -# names (e.g. "org.kde.kdecoration2") and kglobalaccel componentUnique names -# (e.g. "org.kde.dolphin.desktop") routinely contain their own dots -- so it's -# resolved against known-good data instead: the live kglobalaccel component -# list for shortcuts, the kcfg schema's declared group names for everything -# else (falling back to freeform's first-dot split when no schema matches). -def parse_identifier(identifier, kcfg_map): - file, sep, rest = identifier.partition(".") - if not sep or not rest: - raise ValueError(f"invalid identifier {identifier!r} (expected file.group.key)") - - if file == "kglobalshortcutsrc": - group, key = _split_shortcut_group_key(rest) - else: - group, key = _split_schema_group_key(file, rest, kcfg_map) - - return Setting(file, group, key) - - -def load_manifest(path): - entries = {} - if not path.exists(): - return entries - for line in path.read_text().splitlines(): - if not line.strip(): - continue - identifier, _, value = line.partition("=") - entries[identifier] = value - return entries - - -def write_manifest(path, entries): - lines = [f"{identifier}={value}" for identifier, value in entries.items()] - path.write_text("".join(f"{line}\n" for line in lines)) - - -def _parse_kcfg(path): - try: - return ET.parse(path).getroot() - except ET.ParseError: - return None - - -def _kcfgfile_name(root): - elem = root.find(f"{KCFG_NS}kcfgfile") - if elem is None: - return None - return elem.get("name") - - -def build_kcfg_map(schema_dir): - mapping = defaultdict(list) - if not schema_dir.is_dir(): - return mapping - - for path in sorted(schema_dir.glob("*.kcfg")): - root = _parse_kcfg(path) - if root is None: - continue - - rcfile = _kcfgfile_name(root) or ARG_TRUE_RCFILES.get(path.name) - if rcfile: - mapping[rcfile].append(path) - - return mapping - - -def find_schema_default(kcfg_paths, setting): - for path in kcfg_paths: - root = _parse_kcfg(path) - if root is None: - continue - - for group_elem in root.iter(f"{KCFG_NS}group"): - if group_elem.get("name") != setting.group: - continue - for entry in group_elem.findall(f"{KCFG_NS}entry"): - if (entry.get("key") or entry.get("name")) != setting.key: - continue - default_elem = entry.find(f"{KCFG_NS}default") - return default_elem.text if default_elem is not None and default_elem.text else "" - - return None - - -def iter_schema_identifiers(kcfg_map): - for rcfile, paths in kcfg_map.items(): - for path in paths: - root = _parse_kcfg(path) - if root is None: - continue - - for group_elem in root.iter(f"{KCFG_NS}group"): - group = group_elem.get("name") - if not group: - continue - for entry in group_elem.findall(f"{KCFG_NS}entry"): - key = entry.get("key") or entry.get("name") - if key: - yield Setting(rcfile, group, key) - - -def resolve_mechanism(setting, kcfg_map): - if setting.file == "kglobalshortcutsrc": - return "shortcuts", None - - default = find_schema_default(kcfg_map.get(setting.file, []), setting) - if default is not None: - return "schema", default - - return "freeform", None - - -def read_live_value(setting, default): - cmd = ["kreadconfig6", "--file", setting.file, "--group", setting.group, "--key", setting.key] - if default is not None: - cmd += ["--default", default] - - result = subprocess.run(cmd, capture_output=True, text=True) - if result.returncode != 0: - raise RuntimeError( - f"kreadconfig6 failed for {setting.file}/{setting.group}/{setting.key}: {result.stderr.strip()}" - ) - return result.stdout.rstrip("\n") - - -def write_live_value(setting, value): - cmd = [ - "kwriteconfig6", - "--file", setting.file, - "--group", setting.group, - "--key", setting.key, - "--", - value, - ] - result = subprocess.run(cmd, capture_output=True, text=True) - if result.returncode != 0: - raise RuntimeError( - f"kwriteconfig6 failed for {setting.file}/{setting.group}/{setting.key}: {result.stderr.strip()}" - ) - - -def _key_sequence_class(): - try: - from PyQt6.QtGui import QKeySequence - except ImportError as e: - raise RuntimeError( - "the shortcuts mechanism requires PyQt6 (install python-pyqt6) to translate key names" - ) from e - return QKeySequence - - -def _keys_to_string(key_ints): - QKeySequence = _key_sequence_class() - return "\t".join(QKeySequence(key).toString() for key in key_ints) - - -def _string_to_keys(value): - if not value: - return [] - - QKeySequence = _key_sequence_class() - keys = [] - for part in value.split("\t"): - part = part.strip() - if not part or part.lower() == "none": - continue - sequence = QKeySequence(part) - if sequence.count() != 1: - raise RuntimeError(f"invalid key sequence {part!r} (expected exactly one key combination)") - keys.append(int(sequence[0].toCombined())) - return keys - - -def _kglobalaccel_call(method, signature, *tokens): - cmd = ["busctl", "--user", "--json=short", "call", - KGLOBALACCEL_SERVICE, KGLOBALACCEL_PATH, KGLOBALACCEL_IFACE, method] - if signature: - cmd += [signature, *(str(token) for token in tokens)] - - result = subprocess.run(cmd, capture_output=True, text=True) - if result.returncode != 0: - raise RuntimeError(f"kglobalaccel {method} failed: {result.stderr.strip()}") - return json.loads(result.stdout)["data"] - - -def _actions_for_component(component_unique): - (actions,) = _kglobalaccel_call("allActionsForComponent", "as", 1, component_unique) - return actions - - -def iter_shortcut_identifiers(): - (components,) = _kglobalaccel_call("allMainComponents", None) - for component in components: - for action in _actions_for_component(component[0]): - yield Setting("kglobalshortcutsrc", action[0], action[1]) - - -def _resolve_shortcut_action_id(component_unique, action_unique): - for action in _actions_for_component(component_unique): - if action[0] == component_unique and action[1] == action_unique: - return action - - raise RuntimeError( - f"no shortcut action {action_unique!r} in component {component_unique!r} " - "(the owning application may need to run once to register its shortcuts with kglobalaccel)" - ) - - -# The plural *Keys methods (a(ai), one 4-int QKeyCombination chord slot per bound -# key sequence) are used instead of the singular shortcut()/defaultShortcut()/ -# setShortcut() methods the flat ai signature suggests: on this KF6 build, -# defaultShortcut() was empirically found to just mirror shortcut() -- returning -# whatever the *current* value is rather than the true packaged default -- while -# defaultShortcutKeys() correctly returns the untouched default even after -# setShortcutKeys() has changed the current value. Only single, non-chorded key -# combinations are supported (see _string_to_keys), so only the first of each -# chord's 4 int slots is ever meaningful here; the rest are always 0. -def _keys_from_chords(chords): - return [chord[0][0] for chord in chords] - - -def read_shortcut_value(component_unique, action_unique, method="shortcutKeys"): - action_id = _resolve_shortcut_action_id(component_unique, action_unique) - (chords,) = _kglobalaccel_call(method, "as", len(action_id), *action_id) - return _keys_to_string(_keys_from_chords(chords)) - - -def write_shortcut_value(component_unique, action_unique, value): - action_id = _resolve_shortcut_action_id(component_unique, action_unique) - keys = _string_to_keys(value) - - tokens = [len(action_id), *action_id, len(keys)] - for key in keys: - tokens += [4, key, 0, 0, 0] - tokens.append(SHORTCUT_NO_AUTOLOADING) - - _kglobalaccel_call("setShortcutKeys", "asa(ai)u", *tokens) - - -def save_one(identifier, kcfg_map): - setting = parse_identifier(identifier, kcfg_map) - mechanism, default = resolve_mechanism(setting, kcfg_map) - if mechanism == "shortcuts": - return read_shortcut_value(setting.group, setting.key) - return read_live_value(setting, default) - - -def apply_one(identifier, value, kcfg_map): - setting = parse_identifier(identifier, kcfg_map) - mechanism, _default = resolve_mechanism(setting, kcfg_map) - if mechanism == "shortcuts": - write_shortcut_value(setting.group, setting.key, value) - return - write_live_value(setting, value) - - -def cmd_save(args, manifest_path, schema_dir): - if args and args[0] == "help": - print(SAVE_USAGE) - return 0 - - if len(args) > 1: - print("dot kde save: too many arguments", file=sys.stderr) - return 1 - - kcfg_map = build_kcfg_map(schema_dir) - manifest = load_manifest(manifest_path) - - try: - if args: - manifest[args[0]] = save_one(args[0], kcfg_map) - else: - for identifier in manifest: - manifest[identifier] = save_one(identifier, kcfg_map) - except (ValueError, RuntimeError) as e: - print(f"dot kde save: {e}", file=sys.stderr) - return 1 - - write_manifest(manifest_path, manifest) - return 0 - - -def cmd_apply(args, manifest_path, schema_dir): - if args and args[0] == "help": - print(APPLY_USAGE) - return 0 - - if args: - print("dot kde apply: too many arguments", file=sys.stderr) - return 1 - - kcfg_map = build_kcfg_map(schema_dir) - manifest = load_manifest(manifest_path) - - try: - for identifier, value in manifest.items(): - apply_one(identifier, value, kcfg_map) - except (ValueError, RuntimeError) as e: - print(f"dot kde apply: {e}", file=sys.stderr) - return 1 - - return 0 - - -def cmd_diff(args, manifest_path, schema_dir): - if args and args[0] == "help": - print(DIFF_USAGE) - return 0 - - if args: - print("dot kde diff: too many arguments", file=sys.stderr) - return 1 - - kcfg_map = build_kcfg_map(schema_dir) - manifest = load_manifest(manifest_path) - - for setting in sorted(set(iter_schema_identifiers(kcfg_map))): - identifier = f"{setting.file}.{setting.group}.{setting.key}" - default = find_schema_default(kcfg_map.get(setting.file, []), setting) - try: - live = read_live_value(setting, default) - except RuntimeError as e: - print(f"dot kde diff: {e}", file=sys.stderr) - continue - - if live == default: - continue - - tag = "declared" if identifier in manifest else "undeclared" - print(f"{tag} {identifier} = {live} (default: {default})") - - # Shortcuts are enumerable via kglobalaccel's allMainComponents/ - # allActionsForComponent (the same source iter_shortcut_identifiers already - # walks for tab-completion), so unlike freeform they can participate in - # broad undeclared-drift discovery too. - try: - shortcut_settings = sorted(set(iter_shortcut_identifiers())) - except (RuntimeError, OSError) as e: - print(f"dot kde diff: shortcuts scan unavailable: {e}", file=sys.stderr) - shortcut_settings = [] - - for setting in shortcut_settings: - identifier = f"{setting.file}.{setting.group}.{setting.key}" - try: - live = read_shortcut_value(setting.group, setting.key) - default = read_shortcut_value(setting.group, setting.key, method="defaultShortcutKeys") - except RuntimeError as e: - print(f"dot kde diff: {e}", file=sys.stderr) - continue - - if live == default: - continue - - tag = "declared" if identifier in manifest else "undeclared" - print(f"{tag} {identifier} = {live} (default: {default})") - - # Freeform settings have no schema to enumerate from, so unlike the - # schema-backed and shortcuts scans above, they can only be checked by - # walking identifiers already in the manifest -- they never surface an - # undeclared setting via broad scan. Shortcuts entries are skipped here - # (rather than re-parsed) since the broad-scan pass above already reports - # every declared shortcut mismatch; parsing one here would also mean an - # extra live kglobalaccel round-trip per entry for no benefit. - for identifier in manifest: - if identifier.split(".", 1)[0] == "kglobalshortcutsrc": - continue - try: - setting = parse_identifier(identifier, kcfg_map) - mechanism, default = resolve_mechanism(setting, kcfg_map) - if mechanism != "freeform": - continue - live = read_live_value(setting, default) - if live == "": - continue - except (ValueError, RuntimeError) as e: - print(f"dot kde diff: {e}", file=sys.stderr) - continue - - print(f"declared {identifier} = {live} (default: {default or ''})") - - return 0 - - -def cmd_complete(schema_dir): - kcfg_map = build_kcfg_map(schema_dir) - for setting in sorted(set(iter_schema_identifiers(kcfg_map))): - print(f"{setting.file}.{setting.group}.{setting.key}") - - try: - # Fish's completion runs this on every TAB press, in shells that may have no - # live KDE session (or no busctl at all) -- a broken shortcuts source must - # never cost the schema-backed candidates already printed above. - shortcut_settings = sorted(set(iter_shortcut_identifiers())) - except (RuntimeError, OSError): - shortcut_settings = [] - - for setting in shortcut_settings: - print(f"{setting.file}.{setting.group}.{setting.key}") - - return 0 - - -def main(argv): - if not argv: - print("dot kde: no command given", file=sys.stderr) - return 1 - - command, rest = argv[0], argv[1:] - schema_dir = Path(os.environ.get("DOT_KDE_KCFG_DIR", DEFAULT_SCHEMA_DIR)) - manifest_path = Path(os.environ["HOME"]) / ".config" / "dot" / "kde-manifest" - - if command == "save": - return cmd_save(rest, manifest_path, schema_dir) - - if command == "apply": - return cmd_apply(rest, manifest_path, schema_dir) - - if command == "diff": - return cmd_diff(rest, manifest_path, schema_dir) - - # Internal, not a user-facing `dot kde` subcommand -- called directly by - # completions/dot.fish to source candidates from the live schema, never - # dispatched to via kde.fish. - if command == "complete": - return cmd_complete(schema_dir) - - print(f"dot kde: unknown command {command!r}", file=sys.stderr) - return 1 - - -if __name__ == "__main__": - sys.exit(main(sys.argv[1:])) diff --git a/.config/dot/commands/setup/folders.fish b/.config/dot/commands/setup/folders.fish deleted file mode 100644 index be70962..0000000 --- a/.config/dot/commands/setup/folders.fish +++ /dev/null @@ -1,134 +0,0 @@ -function _dot_setup_folders_usage - echo "usage: dot setup folders [--dry-run] - -Brings the 8 standard XDG user directories under the project's fixed -short-name convention (Desktop -> .desktop, Documents -> doc, Downloads -> -dwn, Music -> mus, Pictures -> pic, Videos -> vid, Templates/Public -> -.ignoreme). This mapping is fixed and does not depend on -~/.config/user-dirs.dirs, which is a separate, manually tracked dotfile -this command never reads or writes. - -Content left behind in a legacy full-named folder (e.g. ~/Documents) by a -fresh XDG-defaults install -- empty or not -- is merged into its short-named -replacement. A nested Pictures/Screenshots folder is renamed to -pic/screenshots as part of the same pass. - -An entry that collides by name with something already in the short-named -target is never overwritten: it's skipped, reported, and its legacy folder is -left in place (not removed) even when everything else in it migrated. - - --dry-run report what would move and what would be skipped as a - collision, without changing anything on disk - -Runs xdg-user-dirs-update once afterwards to notify running apps/portals -(skipped under --dry-run)." -end - -function _dot_setup_folders - if test "$argv[1]" = help - _dot_setup_folders_usage - return 0 - end - - argparse 'dry-run' -- $argv - or return 1 - - # Fixed legacy-name -> short-name mapping. Deliberately hardcoded rather - # than read from ~/.config/user-dirs.dirs: that file is a separate, - # manually tracked dotfile whose XDG_*_DIR values can drift or go stale - # (or never get edited to the short names at all), and this command's - # own migration logic must not depend on it being correct. - set -l legacy_names Desktop Documents Downloads Music Pictures Videos Templates Public - set -l target_names .desktop doc dwn mus pic vid .ignoreme .ignoreme - - for i in (seq (count $legacy_names)) - set -l legacy_name $legacy_names[$i] - set -l target_rel $target_names[$i] - set -l target_path $HOME/$target_rel - set -l legacy_path $HOME/$legacy_name - - if not set -q _flag_dry_run - mkdir -p $target_path - end - - if not test -d $legacy_path - continue - end - - # Screenshots is always moved as one atomic unit (renamed to - # lowercase screenshots), so its individual files must never appear - # as separate move/report entries. - set -l screenshots_path $legacy_path/Screenshots - set -l top_level_entries (find $legacy_path -mindepth 1 -maxdepth 1 -not -name Screenshots) - - # No-clobber: an entry whose name already exists in the target is - # never moved over. It's collected here and reported below; its - # legacy folder is left in place (not removed) if any collision - # occurred, even though everything else in it migrated successfully. - set -l collisions - set -l movable_entries - set -l screenshots_movable 0 - - if test -d $screenshots_path - if test -e $target_path/screenshots - set -a collisions $screenshots_path - else - set screenshots_movable 1 - end - end - - for entry in $top_level_entries - if test -e $target_path/(path basename $entry) - set -a collisions $entry - else - set -a movable_entries $entry - end - end - - set -l movable_count (count $movable_entries) - set -l entry_word entries - test $movable_count -eq 1 - and set entry_word entry - - if set -q _flag_dry_run - if test $screenshots_movable -eq 1 - echo "dot setup folders: would move $screenshots_path to $target_path/screenshots" - end - if test $movable_count -gt 0 - echo "dot setup folders: would move $movable_count $entry_word from ~/$legacy_name to ~/$target_rel" - end - if test (count $collisions) -gt 0 - echo "dot setup folders: ~/$legacy_name has entries already present in ~/$target_rel, would skip (not overwritten):" - for c in $collisions - echo " $c" - end - echo "dot setup folders: ~/$legacy_name would remain in place due to the collision(s) above" - end - continue - end - - if test $screenshots_movable -eq 1 - mv -n $screenshots_path $target_path/screenshots - echo "dot setup folders: moved $screenshots_path to $target_path/screenshots" - end - - if test $movable_count -gt 0 - mv -n $movable_entries $target_path/ - echo "dot setup folders: moved $movable_count $entry_word from ~/$legacy_name to ~/$target_rel" - end - - if test (count $collisions) -gt 0 - echo "dot setup folders: ~/$legacy_name has entries already present in ~/$target_rel, skipping (not overwritten):" - for c in $collisions - echo " $c" - end - echo "dot setup folders: leaving ~/$legacy_name in place due to the collision(s) above" - else - rmdir $legacy_path - end - end - - if not set -q _flag_dry_run - xdg-user-dirs-update - end -end diff --git a/.config/dot/commands/setup/setup.fish b/.config/dot/commands/setup/setup.fish deleted file mode 100644 index 0bdf813..0000000 --- a/.config/dot/commands/setup/setup.fish +++ /dev/null @@ -1,35 +0,0 @@ -function _dot_setup_usage - echo "usage: dot setup [] - -Tasks: - folders bring the 8 standard XDG user directories under the short-name convention - help show this message - -Run 'dot setup help' for details on a specific task. - -With no task given, runs every setup task." -end - -function _dot_setup - if test "$argv[1]" = help - _dot_setup_usage - return 0 - end - - set -l helper_dir (status dirname) - source $helper_dir/folders.fish - - if test -z "$argv[1]" - _dot_setup_folders - return $status - end - - switch $argv[1] - case folders - _dot_setup_folders $argv[2..-1] - return $status - case '*' - _dot_setup_usage - return 1 - end -end diff --git a/.config/dot/commands/vpn.fish b/.config/dot/commands/vpn.fish deleted file mode 100644 index 41057ba..0000000 --- a/.config/dot/commands/vpn.fish +++ /dev/null @@ -1,29 +0,0 @@ -function _dot_vpn_usage - echo "usage: dot vpn - -Commands: - up bring the UDM-PRO-Laptop WireGuard connection up - down bring the UDM-PRO-Laptop WireGuard connection down - help show this message" -end - -function _dot_vpn - if test "$argv[1]" = help - _dot_vpn_usage - return 0 - end - - set -l connection UDM-PRO-Laptop - - switch "$argv[1]" - case up - nmcli connection up $connection - return $status - case down - nmcli connection down $connection - return $status - case '*' - _dot_vpn_usage - return 1 - end -end diff --git a/.config/dot/kde-manifest b/.config/dot/kde-manifest deleted file mode 100644 index a28fce2..0000000 --- a/.config/dot/kde-manifest +++ /dev/null @@ -1,32 +0,0 @@ -kxkbrc.Layout.Options=caps:escape_shifted_capslock -kglobalshortcutsrc.ksmserver.Lock Session=Meta+L Screensaver -kglobalshortcutsrc.kwin.Window to Desktop 1=Meta+! -kglobalshortcutsrc.kwin.Window to Desktop 2=Meta+@ -kglobalshortcutsrc.kwin.Window to Desktop 3=Meta+# -kglobalshortcutsrc.kwin.Window to Desktop 4=Meta+$ -kglobalshortcutsrc.kwin.Window to Desktop 5=Meta+% -kglobalshortcutsrc.kwin.Window to Desktop 6=Meta+^ -kglobalshortcutsrc.kwin.Window to Desktop 7=Meta+& -kglobalshortcutsrc.kwin.Window to Desktop 8=Meta+* -kglobalshortcutsrc.kwin.Window to Desktop 9=Meta+( -kglobalshortcutsrc.kwin.Switch to Desktop 1=Meta+1 -kglobalshortcutsrc.kwin.Switch to Desktop 2=Meta+2 -kglobalshortcutsrc.kwin.Switch to Desktop 3=Meta+3 -kglobalshortcutsrc.kwin.Switch to Desktop 4=Meta+4 -kglobalshortcutsrc.kwin.Switch to Desktop 5=Meta+5 -kglobalshortcutsrc.kwin.Switch to Desktop 6=Meta+6 -kglobalshortcutsrc.kwin.Switch to Desktop 7=Meta+7 -kglobalshortcutsrc.kwin.Switch to Desktop 8=Meta+8 -kglobalshortcutsrc.kwin.Switch to Desktop 9=Meta+9 -kglobalshortcutsrc.plasmashell.activate task manager entry 1= -kglobalshortcutsrc.plasmashell.activate task manager entry 2= -kglobalshortcutsrc.plasmashell.activate task manager entry 3= -kglobalshortcutsrc.plasmashell.activate task manager entry 4= -kglobalshortcutsrc.plasmashell.activate task manager entry 5= -kglobalshortcutsrc.plasmashell.activate task manager entry 6= -kglobalshortcutsrc.plasmashell.activate task manager entry 7= -kglobalshortcutsrc.plasmashell.activate task manager entry 8= -kglobalshortcutsrc.plasmashell.activate task manager entry 9= -kglobalshortcutsrc.Alacritty.desktop._launch=Meta+Return -kglobalshortcutsrc.org.kde.konsole.desktop._launch= -kglobalshortcutsrc.kwin.Window Close=Meta+Shift+Q diff --git a/.config/dot/packages/pacman b/.config/dot/packages/pacman deleted file mode 100644 index 307bc24..0000000 --- a/.config/dot/packages/pacman +++ /dev/null @@ -1,2 +0,0 @@ -neovim -tmux diff --git a/.config/dot/tests/dot.fish b/.config/dot/tests/dot.fish deleted file mode 100644 index 3bae56a..0000000 --- a/.config/dot/tests/dot.fish +++ /dev/null @@ -1,1000 +0,0 @@ -set -l commands_dir (path resolve (status dirname)/../commands) - -# Fixture: a fake bare "remote" repo with tracked dotfiles, shared read-only -# across every case below. dot init only ever clones from it, never mutates it. -set -l remote (mktemp -d)/dotfiles.git -git init -q --bare $remote - -set -l seed (mktemp -d) -pushd $seed -git init -q -b main -git config user.email test@dot.fish -git config user.name dot-tests -mkdir -p .config/fish/functions -echo 'echo tracked-bashrc' >.bashrc -echo 'echo hi' >.config/fish/functions/greet.fish -git add -A -git commit -qm seed >/dev/null -git remote add origin $remote -git push -q origin HEAD:main >/dev/null 2>&1 -popd -git --git-dir=$remote symbolic-ref HEAD refs/heads/main - -# --- fresh bootstrap, no conflicts --- -set -gx HOME (mktemp -d) -dot init --url $remote >/dev/null 2>&1 -set -l fresh_status $status - -@test "dot init succeeds against a clean HOME" $fresh_status -eq 0 -@test "clones the bare repo to ~/.dotfiles" -e $HOME/.dotfiles -@test "checks out tracked files onto HOME" -e $HOME/.bashrc -@test "checked-out file has the repo's content" (cat $HOME/.bashrc) = "echo tracked-bashrc" -@test "disables status.showUntrackedFiles" (git --git-dir=$HOME/.dotfiles config --local status.showuntrackedfiles) = no - -dot init --url $remote >/dev/null 2>&1 -set -l repeat_status $status -@test "re-running dot init refuses when already initialized" $repeat_status -eq 1 - -set -l passthrough_status (dot status >/dev/null 2>&1; echo $status) -@test "git passthrough still works (dot status)" $passthrough_status -eq 0 - -# --- a pre-existing conflicting file gets backed up, not clobbered --- -set -gx HOME (mktemp -d) -echo 'pre-existing-content' >$HOME/.bashrc -dot init --url $remote >/dev/null 2>&1 -set -l conflict_status $status - -@test "dot init still succeeds with a conflicting file present" $conflict_status -eq 0 -@test "conflicting file ends up with the tracked content" (cat $HOME/.bashrc) = "echo tracked-bashrc" -@test "a backup directory was created" -d $HOME/.dotfiles-backup -@test "the pre-existing content was preserved in the backup" (cat $HOME/.dotfiles-backup/*/.bashrc) = "pre-existing-content" - -# --- an unreachable URL never falls back to creating an empty repo --- -set -gx HOME (mktemp -d) -dot init --url /nonexistent/path.git >/dev/null 2>&1 -set -l bad_url_status $status -set -l dotfiles_exists (test -e $HOME/.dotfiles; and echo yes; or echo no) - -@test "dot init fails on an unreachable URL" $bad_url_status -eq 1 -@test "no .dotfiles directory is left behind on failure" $dotfiles_exists = no - -# --- dispatches to files under ~/.config/dot/commands/ without polluting -# the fish function namespace: the file only defines _dot_, which -# only becomes known to fish once dot sources it on demand. -set -gx HOME (mktemp -d) -dot init --url $remote >/dev/null 2>&1 - -mkdir -p $HOME/.config/dot/commands -set -l marker (mktemp) -echo "function _dot_mark - echo marked >$marker -end" >$HOME/.config/dot/commands/mark.fish - -dot mark >/dev/null 2>&1 -@test "dispatches to a command file under ~/.config/dot/commands/" (cat $marker) = marked - -# --- dispatches to a nested commands//.fish, same as a flat file -set -gx HOME (mktemp -d) -dot init --url $remote >/dev/null 2>&1 - -mkdir -p $HOME/.config/dot/commands/nested -set -l nested_marker (mktemp) -echo "function _dot_nested - echo nested-marked >$nested_marker -end" >$HOME/.config/dot/commands/nested/nested.fish - -dot nested >/dev/null 2>&1 -@test "dispatches to a nested commands//.fish" (cat $nested_marker) = nested-marked - -# --- dot help --- -set -gx HOME (mktemp -d) -dot init --url $remote >/dev/null 2>&1 - -set -l help_output (dot help) -set -l help_status $status - -@test "dot help succeeds" $help_status -eq 0 -@test "dot help lists init" (string match -q '*init*' -- $help_output; echo $status) -eq 0 -@test "dot help mentions git passthrough" (string match -q '*git*' -- $help_output; echo $status) -eq 0 -@test "dot help hints at per-command help" (string match -q "*dot help*" -- $help_output; echo $status) -eq 0 - -mkdir -p $HOME/.config/dot/commands -echo "function _dot_mark - echo marked -end" >$HOME/.config/dot/commands/mark.fish - -set -l help_with_custom (dot help) -@test "dot help lists custom commands found under ~/.config/dot/commands/" (string match -q '*mark*' -- $help_with_custom; echo $status) -eq 0 - -mkdir -p $HOME/.config/dot/commands/nested -echo "function _dot_nested - echo nested -end" >$HOME/.config/dot/commands/nested/nested.fish - -set -l help_with_nested (dot help) -@test "dot help lists a nested-directory subcommand" (string match -q '*nested*' -- $help_with_nested; echo $status) -eq 0 - -# --- dot install --- -# pacman and sudo are faked out via a bin dir prepended to PATH: sudo just -# execs its arguments, and pacman logs each invocation to $PACMAN_LOG (one -# line per call) and fails only when asked to install a package literally -# named "failpkg", so tests can force the failure path without touching the -# real package manager. -set -l fake_bin (mktemp -d) -echo '#!/bin/sh -exec "$@"' >$fake_bin/sudo -chmod +x $fake_bin/sudo - -echo '#!/bin/sh -echo "$@" >>"$PACMAN_LOG" -for arg in "$@"; do - if [ "$arg" = failpkg ]; then - exit 1 - fi -done -exit 0' >$fake_bin/pacman -chmod +x $fake_bin/pacman - -set -gx PATH $fake_bin $PATH - -# --- a successful install records the packages, sorted and deduplicated --- -set -gx HOME (mktemp -d) -dot init --url $remote >/dev/null 2>&1 -mkdir -p $HOME/.config/dot/commands -cp $commands_dir/install.fish $HOME/.config/dot/commands/install.fish -set -gx PACMAN_LOG (mktemp) - -dot install zeta alpha >/dev/null 2>&1 -set -l first_install_status $status -set -l list_file $HOME/.config/dot/packages/pacman -set -l synced_by_default (string match -q '*-Sy*' -- (cat $PACMAN_LOG); and echo yes; or echo no) -set -l installed_named (string match -q '*-S --needed zeta alpha*' -- (cat $PACMAN_LOG); and echo yes; or echo no) - -@test "dot install succeeds for real packages" $first_install_status -eq 0 -@test "dot install syncs the database by default" $synced_by_default = yes -@test "dot install passes packages to pacman -S --needed" $installed_named = yes -@test "installed packages are recorded, sorted" (cat $list_file | string collect) = "alpha -zeta" - -dot install beta >/dev/null 2>&1 -@test "a later install merges into the existing list, still sorted" (cat $list_file | string collect) = "alpha -beta -zeta" - -dot install alpha >/dev/null 2>&1 -@test "re-installing an already-recorded package does not duplicate it" (cat $list_file | string collect) = "alpha -beta -zeta" - -# --- --no-sync skips the database refresh --- -set -gx HOME (mktemp -d) -dot init --url $remote >/dev/null 2>&1 -mkdir -p $HOME/.config/dot/commands -cp $commands_dir/install.fish $HOME/.config/dot/commands/install.fish -set -gx PACMAN_LOG (mktemp) - -dot install --no-sync somepkg >/dev/null 2>&1 -set -l synced_with_no_sync (string match -q '*-Sy*' -- (cat $PACMAN_LOG); and echo yes; or echo no) -@test "--no-sync skips pacman -Sy" $synced_with_no_sync = no - -# --- a failed pacman run records nothing --- -set -gx HOME (mktemp -d) -dot init --url $remote >/dev/null 2>&1 -mkdir -p $HOME/.config/dot/commands -cp $commands_dir/install.fish $HOME/.config/dot/commands/install.fish -set -gx PACMAN_LOG (mktemp) - -dot install failpkg >/dev/null 2>&1 -set -l failed_install_status $status -set -l list_exists_after_failure (test -e $HOME/.config/dot/packages/pacman; and echo yes; or echo no) - -@test "dot install fails when pacman fails" $failed_install_status -ne 0 -@test "a failed install leaves no package list behind" $list_exists_after_failure = no - -# --- no packages and no --restore is a usage error --- -set -gx HOME (mktemp -d) -dot init --url $remote >/dev/null 2>&1 -mkdir -p $HOME/.config/dot/commands -cp $commands_dir/install.fish $HOME/.config/dot/commands/install.fish -set -gx PACMAN_LOG (mktemp) - -dot install >/dev/null 2>&1 -set -l no_args_status $status -set -l pacman_called_no_args (test -s $PACMAN_LOG; and echo yes; or echo no) - -@test "dot install with no arguments and no --restore fails" $no_args_status -ne 0 -@test "dot install with no arguments never calls pacman" $pacman_called_no_args = no - -# --- --restore reinstalls everything from the list without rewriting it --- -set -gx HOME (mktemp -d) -dot init --url $remote >/dev/null 2>&1 -mkdir -p $HOME/.config/dot/commands -cp $commands_dir/install.fish $HOME/.config/dot/commands/install.fish -mkdir -p $HOME/.config/dot/packages -printf 'alpha\nbeta\n' >$HOME/.config/dot/packages/pacman -set -gx PACMAN_LOG (mktemp) - -dot install --restore >/dev/null 2>&1 -set -l restore_status $status -set -l restored_named (string match -q '*-S --needed alpha beta*' -- (cat $PACMAN_LOG); and echo yes; or echo no) - -@test "dot install --restore succeeds" $restore_status -eq 0 -@test "--restore installs every package from the list" $restored_named = yes -@test "--restore does not rewrite the list" (cat $HOME/.config/dot/packages/pacman | string collect) = "alpha -beta" - -# --- --restore with no list yet is an error --- -set -gx HOME (mktemp -d) -dot init --url $remote >/dev/null 2>&1 -mkdir -p $HOME/.config/dot/commands -cp $commands_dir/install.fish $HOME/.config/dot/commands/install.fish -set -gx PACMAN_LOG (mktemp) - -dot install --restore >/dev/null 2>&1 -set -l restore_no_list_status $status - -@test "--restore fails when no package list exists yet" $restore_no_list_status -ne 0 - -# --- --restore and explicit packages are mutually exclusive --- -set -gx HOME (mktemp -d) -dot init --url $remote >/dev/null 2>&1 -mkdir -p $HOME/.config/dot/commands -cp $commands_dir/install.fish $HOME/.config/dot/commands/install.fish -mkdir -p $HOME/.config/dot/packages -printf 'alpha\n' >$HOME/.config/dot/packages/pacman -set -gx PACMAN_LOG (mktemp) - -dot install --restore extra >/dev/null 2>&1 -set -l restore_conflict_status $status -set -l pacman_called_conflict (test -s $PACMAN_LOG; and echo yes; or echo no) - -@test "--restore combined with package names fails" $restore_conflict_status -ne 0 -@test "--restore combined with package names never calls pacman" $pacman_called_conflict = no - -# --- help prints usage instead of touching pacman --- -set -gx HOME (mktemp -d) -dot init --url $remote >/dev/null 2>&1 -mkdir -p $HOME/.config/dot/commands -cp $commands_dir/install.fish $HOME/.config/dot/commands/install.fish -set -gx PACMAN_LOG (mktemp) - -set -l help_output (dot install help) -set -l help_status $status -set -l pacman_called_help (test -s $PACMAN_LOG; and echo yes; or echo no) - -@test "dot install help succeeds" $help_status -eq 0 -@test "dot install help mentions --restore" (string match -q '*--restore*' -- $help_output; echo $status) -eq 0 -@test "dot install help mentions --no-sync" (string match -q '*--no-sync*' -- $help_output; echo $status) -eq 0 -@test "dot install help never calls pacman" $pacman_called_help = no - -# --- dot kde --- -# The fixture schema directory stands in for the real /usr/share/config.kcfg: -# testrc.kcfg declares a plain , kwin.kcfg declares -# (resolved only via the hand-maintained exceptions -# list, kwin.kcfg -> kwinrc), and unmapped.kcfg is an arg="true" schema with -# no exceptions-list entry, so it never resolves to anything. -set -l kcfg_fixtures (path resolve (status dirname)/fixtures/kcfg) -set -gx DOT_KDE_KCFG_DIR $kcfg_fixtures - -# kreadconfig6 itself is never mocked for the tests that exercise real -# save behavior (per the project's convention, it runs for real against -# fixture rc files under the scratch HOME) -- only the help-path tests below -# swap in a logging fake, to prove kreadconfig6 is never invoked for them. -set -l path_before_fake_kreadconfig $PATH - -# --- dot kde help / dot kde save help touch neither the manifest nor kreadconfig6 --- -set -gx HOME (mktemp -d) -dot init --url $remote >/dev/null 2>&1 -mkdir -p $HOME/.config/dot/commands/kde -cp $commands_dir/kde/kde.fish $HOME/.config/dot/commands/kde/kde.fish -cp $commands_dir/kde/kde.py $HOME/.config/dot/commands/kde/kde.py - -set -l fake_bin_kde (mktemp -d) -set -gx KREADCONFIG_LOG (mktemp) -echo '#!/bin/sh -echo "$@" >>"$KREADCONFIG_LOG" -exit 1' >$fake_bin_kde/kreadconfig6 -chmod +x $fake_bin_kde/kreadconfig6 -set -gx PATH $fake_bin_kde $PATH - -set -l kde_help_output (dot kde help) -set -l kde_help_status $status -set -l kreadconfig_called_for_kde_help (test -s $KREADCONFIG_LOG; and echo yes; or echo no) -set -l manifest_exists_after_kde_help (test -e $HOME/.config/dot/kde-manifest; and echo yes; or echo no) - -@test "dot kde help succeeds" $kde_help_status -eq 0 -@test "dot kde help mentions save" (string match -q '*save*' -- $kde_help_output; echo $status) -eq 0 -@test "dot kde help never invokes kreadconfig6" $kreadconfig_called_for_kde_help = no -@test "dot kde help does not create a manifest" $manifest_exists_after_kde_help = no - -set -l kde_save_help_output (dot kde save help) -set -l kde_save_help_status $status -set -l kreadconfig_called_for_save_help (test -s $KREADCONFIG_LOG; and echo yes; or echo no) -set -l manifest_exists_after_save_help (test -e $HOME/.config/dot/kde-manifest; and echo yes; or echo no) - -@test "dot kde save help succeeds" $kde_save_help_status -eq 0 -@test "dot kde save help mentions identifier" (string match -q '*identifier*' -- $kde_save_help_output; echo $status) -eq 0 -@test "dot kde save help never invokes kreadconfig6" $kreadconfig_called_for_save_help = no -@test "dot kde save help does not create a manifest" $manifest_exists_after_save_help = no - -set -gx PATH $path_before_fake_kreadconfig - -# --- dot kde save : declares a new manifest entry from the real live value --- -set -gx HOME (mktemp -d) -dot init --url $remote >/dev/null 2>&1 -mkdir -p $HOME/.config/dot/commands/kde -cp $commands_dir/kde/kde.fish $HOME/.config/dot/commands/kde/kde.fish -cp $commands_dir/kde/kde.py $HOME/.config/dot/commands/kde/kde.py -mkdir -p $HOME/.config -printf '[General]\nGreeting=Hi=There\n' >$HOME/.config/testrc -set -l manifest $HOME/.config/dot/kde-manifest - -dot kde save testrc.General.Greeting >/dev/null 2>&1 -set -l save_status $status - -@test "dot kde save succeeds" $save_status -eq 0 -@test "declares the identifier with its live value, preserving an embedded '='" (cat $manifest | string collect) = "testrc.General.Greeting=Hi=There" - -# a kcfg entry whose ini key (key=) differs from its schema name still -# resolves correctly, falling back to the schema default when unset live -dot kde save testrc.General.RealKey >/dev/null 2>&1 -@test "resolves an aliased kcfg key (name != key) to its schema default" (string match -q '*testrc.General.RealKey=AliasDefault*' -- (cat $manifest); echo $status) -eq 0 - -# the identifier is split on the first two dots only, so the key portion -# may itself contain further dots and spaces -dot kde save "testrc.General.Some.Key With Spaces" >/dev/null 2>&1 -@test "an identifier's key portion may contain further dots and spaces" (string match -q '*testrc.General.Some.Key With Spaces=SpacedDefault*' -- (cat $manifest); echo $status) -eq 0 - -# an arg="true" schema resolves through the hand-maintained exceptions list -# (kwin.kcfg -> kwinrc), not by scanning for a static -dot kde save kwinrc.Windows.BorderSize >/dev/null 2>&1 -@test "resolves an arg=true schema via the hand-maintained exceptions list" (string match -q '*kwinrc.Windows.BorderSize=Normal*' -- (cat $manifest); echo $status) -eq 0 - -set -l declared_count_before_freeform (cat $manifest | count) - -# a setting whose rc file never appears in the mapping table falls to the -# freeform branch: read/write directly via kreadconfig6/kwriteconfig6, with -# "default" meaning "the key is absent" rather than any schema value -printf '[Group]\nKey=FreeformValue\n' >$HOME/.config/somefreeform -dot kde save somefreeform.Group.Key >/dev/null 2>&1 -set -l freeform_save_status $status -set -l declared_count_after_freeform (cat $manifest | count) - -@test "dot kde save succeeds for a freeform (unmapped rc file) identifier" $freeform_save_status -eq 0 -@test "declares the freeform identifier with its real live value" (string match -q '*somefreeform.Group.Key=FreeformValue*' -- (cat $manifest); echo $status) -eq 0 -@test "a freeform save adds exactly one manifest entry" $declared_count_after_freeform -eq (math $declared_count_before_freeform + 1) - -# an arg="true" schema *absent* from the exceptions list (unmapped.kcfg) -# must not be guessed at (e.g. from its own filename) -- it contributes -# nothing to the mapping table, so its settings fall to freeform too. Proven -# here by reading with the key absent: a schema-backed read would fall back -# to the schema's declared default ("Unreachable"); freeform's "default" is -# instead "the key is absent", so it reads empty. -dot kde save unmapped.Whatever.Setting >/dev/null 2>&1 -set -l unlisted_arg_true_status $status - -@test "an arg=true schema missing from the exceptions list resolves to freeform, not schema" $unlisted_arg_true_status -eq 0 -@test "a freeform read never falls back to another schema's default" (string match -q '*Unreachable*' -- (cat $manifest); echo $status) -eq 1 -@test "a freeform read of an absent key stores an empty value" (string match -q '*unmapped.Whatever.Setting=*' -- (cat $manifest); echo $status) -eq 0 - -# the shortcuts mechanism (kglobalshortcutsrc -> kglobalaccel D-Bus calls) is -# deliberately excluded from this suite -- it depends on a live, already-running -# session service not practically substitutable without disproportionate mock -# infrastructure. Verified manually against the real session instead. - -set -l declared_count_before_refresh (cat $manifest | count) - -# --- dot kde save with no arguments refreshes every already-declared entry --- -printf '[General]\nGreeting=Changed\n' >$HOME/.config/testrc -printf '[Group]\nKey=RefreshedFreeform\n' >$HOME/.config/somefreeform -dot kde save >/dev/null 2>&1 -set -l refresh_status $status -set -l declared_count_after_refresh (cat $manifest | count) - -@test "dot kde save with no arguments succeeds" $refresh_status -eq 0 -@test "refreshes an already-declared schema-backed entry's value from the live system" (string match -q '*testrc.General.Greeting=Changed*' -- (cat $manifest); echo $status) -eq 0 -@test "refreshes an already-declared freeform entry's value from the live system" (string match -q '*somefreeform.Group.Key=RefreshedFreeform*' -- (cat $manifest); echo $status) -eq 0 -@test "refresh leaves other already-declared entries untouched" (string match -q '*testrc.General.RealKey=AliasDefault*' -- (cat $manifest); echo $status) -eq 0 -@test "refresh adds no new undeclared entries" $declared_count_after_refresh -eq $declared_count_before_refresh - -# --- misuse: too many arguments / a malformed identifier --- -dot kde save one two >/dev/null 2>&1 -set -l too_many_args_status $status -@test "dot kde save rejects more than one identifier" $too_many_args_status -ne 0 - -dot kde save nodots >/dev/null 2>&1 -set -l bad_identifier_status $status -@test "dot kde save rejects an identifier without file.group.key structure" $bad_identifier_status -ne 0 - -# --- dot kde apply help touches neither the manifest nor kwriteconfig6 --- -set -gx HOME (mktemp -d) -dot init --url $remote >/dev/null 2>&1 -mkdir -p $HOME/.config/dot/commands/kde -cp $commands_dir/kde/kde.fish $HOME/.config/dot/commands/kde/kde.fish -cp $commands_dir/kde/kde.py $HOME/.config/dot/commands/kde/kde.py - -set -l fake_bin_kwrite (mktemp -d) -set -gx KWRITECONFIG_LOG (mktemp) -echo '#!/bin/sh -echo "$@" >>"$KWRITECONFIG_LOG" -exit 1' >$fake_bin_kwrite/kwriteconfig6 -chmod +x $fake_bin_kwrite/kwriteconfig6 -set -gx PATH $fake_bin_kwrite $path_before_fake_kreadconfig - -set -l kde_apply_help_output (dot kde apply help) -set -l kde_apply_help_status $status -set -l kwriteconfig_called_for_apply_help (test -s $KWRITECONFIG_LOG; and echo yes; or echo no) -set -l manifest_exists_after_apply_help (test -e $HOME/.config/dot/kde-manifest; and echo yes; or echo no) - -@test "dot kde apply help succeeds" $kde_apply_help_status -eq 0 -@test "dot kde apply help mentions manifest" (string match -q '*manifest*' -- $kde_apply_help_output; echo $status) -eq 0 -@test "dot kde apply help never invokes kwriteconfig6" $kwriteconfig_called_for_apply_help = no -@test "dot kde apply help does not create a manifest" $manifest_exists_after_apply_help = no - -set -gx PATH $path_before_fake_kreadconfig - -# --- dot kde apply: pushes every declared manifest entry onto the live rc file --- -set -gx HOME (mktemp -d) -dot init --url $remote >/dev/null 2>&1 -mkdir -p $HOME/.config/dot/commands/kde -cp $commands_dir/kde/kde.fish $HOME/.config/dot/commands/kde/kde.fish -cp $commands_dir/kde/kde.py $HOME/.config/dot/commands/kde/kde.py -mkdir -p $HOME/.config/dot -printf 'testrc.General.Greeting=Applied Greeting\ntestrc.General.RealKey=Hi=There\n' >$HOME/.config/dot/kde-manifest - -dot kde apply >/dev/null 2>&1 -set -l apply_status $status -set -l testrc_after_apply (cat $HOME/.config/testrc) - -@test "dot kde apply succeeds" $apply_status -eq 0 -@test "dot kde apply writes a declared value onto the live rc file" (string match -q '*Greeting=Applied Greeting*' -- $testrc_after_apply; echo $status) -eq 0 -@test "dot kde apply preserves an embedded '=' in the applied value" (string match -q '*RealKey=Hi=There*' -- $testrc_after_apply; echo $status) -eq 0 - -# re-running against a system already matching the manifest changes nothing -dot kde apply >/dev/null 2>&1 -set -l reapply_status $status -set -l testrc_after_reapply (cat $HOME/.config/testrc) - -@test "re-running dot kde apply succeeds" $reapply_status -eq 0 -@test "re-running dot kde apply against an already-applied system is idempotent" "$testrc_after_reapply" = "$testrc_after_apply" - -# a manifest entry whose rc file has no schema (freeform) is written -# directly via kwriteconfig6, idempotently, just like a schema-backed entry -printf 'testrc.General.Greeting=Applied Greeting\nsomefreeform.Group.Key=Value\n' >$HOME/.config/dot/kde-manifest -dot kde apply >/dev/null 2>&1 -set -l apply_freeform_status $status -set -l freeformrc_after_apply (cat $HOME/.config/somefreeform) - -@test "dot kde apply succeeds for a manifest with a freeform entry" $apply_freeform_status -eq 0 -@test "dot kde apply writes a freeform entry via kwriteconfig6" (string match -q '*Key=Value*' -- $freeformrc_after_apply; echo $status) -eq 0 - -dot kde apply >/dev/null 2>&1 -set -l freeformrc_after_reapply (cat $HOME/.config/somefreeform) -@test "re-running dot kde apply against an already-applied freeform entry is idempotent" "$freeformrc_after_reapply" = "$freeformrc_after_apply" - -# the shortcuts mechanism is deliberately excluded from this suite -- see the -# note by the `dot kde save` shortcuts exclusion above. - -# misuse: apply takes no arguments -printf 'testrc.General.Greeting=Applied Greeting\n' >$HOME/.config/dot/kde-manifest -dot kde apply extra-arg >/dev/null 2>&1 -set -l apply_extra_arg_status $status -@test "dot kde apply rejects an unexpected argument" $apply_extra_arg_status -ne 0 - -# --- dot kde diff help touches neither the manifest nor kreadconfig6 --- -set -gx HOME (mktemp -d) -dot init --url $remote >/dev/null 2>&1 -mkdir -p $HOME/.config/dot/commands/kde -cp $commands_dir/kde/kde.fish $HOME/.config/dot/commands/kde/kde.fish -cp $commands_dir/kde/kde.py $HOME/.config/dot/commands/kde/kde.py - -set -l fake_bin_kde_diff (mktemp -d) -set -gx KREADCONFIG_LOG (mktemp) -echo '#!/bin/sh -echo "$@" >>"$KREADCONFIG_LOG" -exit 1' >$fake_bin_kde_diff/kreadconfig6 -chmod +x $fake_bin_kde_diff/kreadconfig6 -set -gx PATH $fake_bin_kde_diff $path_before_fake_kreadconfig - -set -l kde_diff_help_output (dot kde diff help) -set -l kde_diff_help_status $status -set -l kreadconfig_called_for_diff_help (test -s $KREADCONFIG_LOG; and echo yes; or echo no) -set -l manifest_exists_after_diff_help (test -e $HOME/.config/dot/kde-manifest; and echo yes; or echo no) - -@test "dot kde diff help succeeds" $kde_diff_help_status -eq 0 -@test "dot kde diff help mentions undeclared" (string match -q '*undeclared*' -- $kde_diff_help_output; echo $status) -eq 0 -@test "dot kde diff help never invokes kreadconfig6" $kreadconfig_called_for_diff_help = no -@test "dot kde diff help does not create a manifest" $manifest_exists_after_diff_help = no - -set -gx PATH $path_before_fake_kreadconfig - -# --- dot kde diff: broad read-only scan over every schema-backed identifier, -# tagging each mismatch declared/undeclared, and skipping settings that -# already match their schema default --- -set -gx HOME (mktemp -d) -dot init --url $remote >/dev/null 2>&1 -mkdir -p $HOME/.config/dot/commands/kde -cp $commands_dir/kde/kde.fish $HOME/.config/dot/commands/kde/kde.fish -cp $commands_dir/kde/kde.py $HOME/.config/dot/commands/kde/kde.py -mkdir -p $HOME/.config/dot - -# Greeting differs from its default and is already declared in the manifest; -# RealKey differs from its default but has never been declared; Some.Key With -# Spaces is left unset, so it falls back to (and matches) its schema default, -# and kwinrc.Windows.BorderSize likewise matches its default via the -# arg=true/exceptions-list mapping -- neither should be reported. On the -# freeform side: Group.Key is declared and present live (a mismatch against -# freeform's "absent" default); Group.AbsentKey is declared but never applied -# live, so it matches the absent default and isn't reported; Other.Undeclared -# is present live but never declared, and must never surface via broad scan -# since freeform has no schema to enumerate from. -printf '[General]\nGreeting=Bonjour\nRealKey=ChangedAlias\n' >$HOME/.config/testrc -printf '[Group]\nKey=CustomValue\n\n[Other]\nUndeclared=ShouldNeverAppear\n' >$HOME/.config/somefreeform -printf 'testrc.General.Greeting=Bonjour\nsomefreeform.Group.Key=CustomValue\nsomefreeform.Group.AbsentKey=NeverApplied\n' >$HOME/.config/dot/kde-manifest -set -l manifest_before_diff (cat $HOME/.config/dot/kde-manifest | string collect) - -set -l diff_output (dot kde diff) -set -l diff_status $status -set -l manifest_after_diff (cat $HOME/.config/dot/kde-manifest | string collect) - -@test "dot kde diff succeeds" $diff_status -eq 0 -@test "dot kde diff tags an already-declared mismatch as declared" (string match -q '*declared testrc.General.Greeting = Bonjour (default: Hello)*' -- $diff_output; echo $status) -eq 0 -@test "dot kde diff tags a never-declared mismatch as undeclared" (string match -q '*undeclared testrc.General.RealKey = ChangedAlias (default: AliasDefault)*' -- $diff_output; echo $status) -eq 0 -@test "dot kde diff does not report a setting matching its default (unset key)" (string match -q '*Some.Key With Spaces*' -- $diff_output; echo $status) -eq 1 -@test "dot kde diff does not report a setting matching its default (arg=true mapping)" (string match -q '*BorderSize*' -- $diff_output; echo $status) -eq 1 -@test "dot kde diff reports an already-declared freeform mismatch (default is absent)" (string match -q '*declared somefreeform.Group.Key = CustomValue (default: )*' -- $diff_output; echo $status) -eq 0 -@test "dot kde diff does not report a declared freeform entry matching its absent default" (string match -q '*AbsentKey*' -- $diff_output; echo $status) -eq 1 -@test "dot kde diff never surfaces an undeclared freeform setting via broad scan" (string match -q '*Undeclared*' -- $diff_output; echo $status) -eq 1 -@test "dot kde diff makes no writes to the manifest" "$manifest_after_diff" = "$manifest_before_diff" - -dot kde diff extra-arg >/dev/null 2>&1 -set -l diff_extra_arg_status $status -@test "dot kde diff rejects an unexpected argument" $diff_extra_arg_status -ne 0 - -# --- kde.py complete: tab-completion candidates, sourced from the live -# schema mapping table rather than a hardcoded list. This is the -# underlying data completions/dot.fish shells out to; the fish -# completion wiring itself is verified manually (no existing -# infrastructure tests completions at all, per the nested-subcommand -# prefactoring task) --- -set -l complete_output (python3 $HOME/.config/dot/commands/kde/kde.py complete) - -@test "kde.py complete lists a schema-backed identifier" (string match -q '*testrc.General.Greeting*' -- $complete_output; echo $status) -eq 0 -@test "kde.py complete resolves an aliased kcfg key to its ini key, not its schema name" (string match -q '*testrc.General.RealKey*' -- $complete_output; echo $status) -eq 0 -@test "kde.py complete lists an arg=true schema resolved via the exceptions list" (string match -q '*kwinrc.Windows.BorderSize*' -- $complete_output; echo $status) -eq 0 -@test "kde.py complete never lists an aliased entry under its schema name" (string match -q '*testrc.General.AliasedKey*' -- $complete_output; echo $status) -eq 1 -@test "kde.py complete never lists an arg=true schema absent from the exceptions list" (string match -q '*Whatever.Setting*' -- $complete_output; echo $status) -eq 1 - -# --- dot help / dot help discovers dot kde --- -set -l help_with_kde (dot help) -@test "dot help lists the kde subcommand" (string match -q '*kde*' -- $help_with_kde; echo $status) -eq 0 - -# --- dot setup folders --- -# The legacy->short-name mapping is fixed in the command itself, not read -# from ~/.config/user-dirs.dirs (a separate, manually tracked dotfile this -# command never reads or writes), so no scenario below needs to seed one. - -# xdg-user-dirs-update is faked out via a PATH-prepended bin that logs each -# invocation, exactly mirroring dot install's fake sudo/pacman. -set -l fake_bin_xdg (mktemp -d) -echo '#!/bin/sh -echo "$@" >>"$XDG_UPDATE_LOG" -exit 0' >$fake_bin_xdg/xdg-user-dirs-update -chmod +x $fake_bin_xdg/xdg-user-dirs-update -set -gx PATH $fake_bin_xdg $PATH - -# --- a fresh migration: all 8 legacy folders present and empty, including a -# nested empty Pictures/Screenshots --- -set -gx HOME (mktemp -d) -dot init --url $remote >/dev/null 2>&1 -mkdir -p $HOME/.config/dot/commands/setup -cp $commands_dir/setup/setup.fish $HOME/.config/dot/commands/setup/setup.fish -cp $commands_dir/setup/folders.fish $HOME/.config/dot/commands/setup/folders.fish -mkdir -p $HOME/Desktop $HOME/Documents $HOME/Downloads $HOME/Music $HOME/Pictures/Screenshots $HOME/Videos $HOME/Templates $HOME/Public -set -gx XDG_UPDATE_LOG (mktemp) - -dot setup folders >/dev/null 2>&1 -set -l fresh_folders_status $status - -@test "dot setup folders succeeds on a fresh scratch HOME" $fresh_folders_status -eq 0 -@test "Desktop is renamed to .desktop" -d $HOME/.desktop -@test "Documents is renamed to doc" -d $HOME/doc -@test "Downloads is renamed to dwn" -d $HOME/dwn -@test "Music is renamed to mus" -d $HOME/mus -@test "Pictures is renamed to pic" -d $HOME/pic -@test "Videos is renamed to vid" -d $HOME/vid -@test "Templates and Public both merge into .ignoreme" -d $HOME/.ignoreme -@test "the nested Screenshots folder is renamed to pic/screenshots" -d $HOME/pic/screenshots -@test "the legacy Desktop folder no longer exists" (test -e $HOME/Desktop; and echo yes; or echo no) = no -@test "the legacy Documents folder no longer exists" (test -e $HOME/Documents; and echo yes; or echo no) = no -@test "the legacy Pictures folder no longer exists" (test -e $HOME/Pictures; and echo yes; or echo no) = no -@test "xdg-user-dirs-update is invoked exactly once" (cat $XDG_UPDATE_LOG | count) -eq 1 - -# --- re-running after a clean migration is a no-op --- -dot setup folders >/dev/null 2>&1 -set -l rerun_status $status - -@test "re-running dot setup folders succeeds" $rerun_status -eq 0 -@test "re-running leaves the short-named folders in place" -d $HOME/pic/screenshots -@test "re-running does not recreate any legacy folder" (test -e $HOME/Pictures; and echo yes; or echo no) = no - -# --- the short-name mapping is fixed regardless of what (if anything) -# ~/.config/user-dirs.dirs declares -- this is the exact real-world bug -# that motivated dropping the dependency: a stale, never-updated -# user-dirs.dirs (still pointing XDG_DOCUMENTS_DIR at ~/Documents itself) -# must not make the target collide with the legacy folder --- -set -gx HOME (mktemp -d) -dot init --url $remote >/dev/null 2>&1 -mkdir -p $HOME/.config/dot/commands/setup -cp $commands_dir/setup/setup.fish $HOME/.config/dot/commands/setup/setup.fish -cp $commands_dir/setup/folders.fish $HOME/.config/dot/commands/setup/folders.fish -mkdir -p $HOME/.config -echo 'XDG_DESKTOP_DIR="$HOME/Desktop" -XDG_DOWNLOAD_DIR="$HOME/Downloads" -XDG_TEMPLATES_DIR="$HOME/" -XDG_PUBLICSHARE_DIR="$HOME/" -XDG_DOCUMENTS_DIR="$HOME/Documents" -XDG_MUSIC_DIR="$HOME/" -XDG_PICTURES_DIR="$HOME/Pictures" -XDG_VIDEOS_DIR="$HOME/Videos"' >$HOME/.config/user-dirs.dirs -mkdir -p $HOME/Documents -echo real-content >$HOME/Documents/report.txt -set -gx XDG_UPDATE_LOG (mktemp) - -dot setup folders >/dev/null 2>&1 - -@test "a stale user-dirs.dirs pointing at the legacy folder itself doesn't confuse the migration" (cat $HOME/doc/report.txt) = real-content -@test "the legacy folder is still removed despite the stale user-dirs.dirs" (test -e $HOME/Documents; and echo yes; or echo no) = no -@test "the stale user-dirs.dirs file itself is left byte-for-byte untouched" (string match -q '*XDG_DOCUMENTS_DIR="$HOME/Documents"*' -- (cat $HOME/.config/user-dirs.dirs); echo $status) -eq 0 - -# --- dot setup folders works even when user-dirs.dirs doesn't exist at all --- -set -gx HOME (mktemp -d) -dot init --url $remote >/dev/null 2>&1 -mkdir -p $HOME/.config/dot/commands/setup -cp $commands_dir/setup/setup.fish $HOME/.config/dot/commands/setup/setup.fish -cp $commands_dir/setup/folders.fish $HOME/.config/dot/commands/setup/folders.fish -mkdir -p $HOME/Documents -echo real-content >$HOME/Documents/report.txt -set -gx XDG_UPDATE_LOG (mktemp) - -set -l no_user_dirs_status -dot setup folders >/dev/null 2>&1 -set no_user_dirs_status $status - -@test "dot setup folders succeeds with no user-dirs.dirs present at all" $no_user_dirs_status -eq 0 -@test "migration still happens with no user-dirs.dirs present at all" (cat $HOME/doc/report.txt) = real-content -@test "no user-dirs.dirs is created as a side effect" (test -e $HOME/.config/user-dirs.dirs; and echo yes; or echo no) = no - -# --- bare `dot setup` (no task given) runs folders as part of running everything --- -set -gx HOME (mktemp -d) -dot init --url $remote >/dev/null 2>&1 -mkdir -p $HOME/.config/dot/commands/setup -cp $commands_dir/setup/setup.fish $HOME/.config/dot/commands/setup/setup.fish -cp $commands_dir/setup/folders.fish $HOME/.config/dot/commands/setup/folders.fish -mkdir -p $HOME/Desktop $HOME/Documents $HOME/Downloads $HOME/Music $HOME/Pictures $HOME/Videos $HOME/Templates $HOME/Public -set -gx XDG_UPDATE_LOG (mktemp) - -dot setup >/dev/null 2>&1 -set -l bare_setup_status $status - -@test "bare dot setup succeeds" $bare_setup_status -eq 0 -@test "bare dot setup runs the folders task" -d $HOME/.desktop -@test "bare dot setup also merges Pictures into pic" -d $HOME/pic - -# --- a non-empty legacy folder merges unconditionally, no flag needed -- -# an unrelated empty legacy folder migrates in the same run --- -set -gx HOME (mktemp -d) -dot init --url $remote >/dev/null 2>&1 -mkdir -p $HOME/.config/dot/commands/setup -cp $commands_dir/setup/setup.fish $HOME/.config/dot/commands/setup/setup.fish -cp $commands_dir/setup/folders.fish $HOME/.config/dot/commands/setup/folders.fish -mkdir -p $HOME/Documents $HOME/Desktop -echo real-content >$HOME/Documents/report.txt -set -gx XDG_UPDATE_LOG (mktemp) - -set -l nonempty_output (dot setup folders 2>&1) -set -l nonempty_status $status - -@test "dot setup folders succeeds when a legacy folder has content" $nonempty_status -eq 0 -@test "a non-empty legacy folder's content is migrated by default" (cat $HOME/doc/report.txt) = real-content -@test "the now-empty legacy folder is removed" (test -e $HOME/Documents; and echo yes; or echo no) = no -@test "prints a message about what was moved" (string match -q '*Documents*' -- $nonempty_output; echo $status) -eq 0 -@test "an unrelated empty legacy folder still migrates in the same run" (test -e $HOME/Desktop; and echo yes; or echo no) = no - -# --- a legacy folder containing only a stray dotfile still migrates by -# default -- there's no separate empty-vs-non-empty gate to trip --- -set -gx HOME (mktemp -d) -dot init --url $remote >/dev/null 2>&1 -mkdir -p $HOME/.config/dot/commands/setup -cp $commands_dir/setup/setup.fish $HOME/.config/dot/commands/setup/setup.fish -cp $commands_dir/setup/folders.fish $HOME/.config/dot/commands/setup/folders.fish -mkdir -p $HOME/Downloads -touch $HOME/Downloads/.directory -set -gx XDG_UPDATE_LOG (mktemp) - -dot setup folders >/dev/null 2>&1 - -@test "a legacy folder holding only a stray dotfile is migrated by default" -e $HOME/dwn/.directory -@test "the legacy folder holding only a stray dotfile is removed" (test -e $HOME/Downloads; and echo yes; or echo no) = no - -# --- a nested empty Screenshots folder migrates alongside unrelated real -# content in the same Pictures folder, all in the same default run --- -set -gx HOME (mktemp -d) -dot init --url $remote >/dev/null 2>&1 -mkdir -p $HOME/.config/dot/commands/setup -cp $commands_dir/setup/setup.fish $HOME/.config/dot/commands/setup/setup.fish -cp $commands_dir/setup/folders.fish $HOME/.config/dot/commands/setup/folders.fish -mkdir -p $HOME/Pictures/Screenshots -echo vacation-photo >$HOME/Pictures/vacation.jpg -set -gx XDG_UPDATE_LOG (mktemp) - -dot setup folders >/dev/null 2>&1 -set -l pictures_with_content_status $status - -@test "dot setup folders succeeds when Pictures has unrelated content" $pictures_with_content_status -eq 0 -@test "the unrelated file in Pictures is migrated into pic" (cat $HOME/pic/vacation.jpg) = vacation-photo -@test "the nested Screenshots folder is renamed to pic/screenshots" -d $HOME/pic/screenshots -@test "the now-empty Pictures folder is removed" (test -e $HOME/Pictures; and echo yes; or echo no) = no - -# --- a Screenshots folder that itself holds real content migrates by -# default too, renamed to pic/screenshots, even when the rest of -# Pictures is empty --- -set -gx HOME (mktemp -d) -dot init --url $remote >/dev/null 2>&1 -mkdir -p $HOME/.config/dot/commands/setup -cp $commands_dir/setup/setup.fish $HOME/.config/dot/commands/setup/setup.fish -cp $commands_dir/setup/folders.fish $HOME/.config/dot/commands/setup/folders.fish -mkdir -p $HOME/Pictures/Screenshots -echo shot-content >$HOME/Pictures/Screenshots/shot.png -set -gx XDG_UPDATE_LOG (mktemp) - -dot setup folders >/dev/null 2>&1 - -@test "a non-empty Screenshots folder migrates by default, renamed to pic/screenshots" (cat $HOME/pic/screenshots/shot.png) = shot-content -@test "the now-empty Pictures folder is removed after migrating Screenshots" (test -e $HOME/Pictures; and echo yes; or echo no) = no - -# --- a filename collision between a legacy folder and its already-populated -# short-named target is skipped (not overwritten), reported, and leaves -# the legacy folder in place -- even when another non-colliding file in -# the same folder is merged successfully --- -set -gx HOME (mktemp -d) -dot init --url $remote >/dev/null 2>&1 -mkdir -p $HOME/.config/dot/commands/setup -cp $commands_dir/setup/setup.fish $HOME/.config/dot/commands/setup/setup.fish -cp $commands_dir/setup/folders.fish $HOME/.config/dot/commands/setup/folders.fish -mkdir -p $HOME/Documents $HOME/doc -echo legacy-content >$HOME/Documents/report.txt -echo legacy-only >$HOME/Documents/notes.txt -echo target-content >$HOME/doc/report.txt -set -gx XDG_UPDATE_LOG (mktemp) - -set -l collision_output (dot setup folders 2>&1) -set -l collision_status $status - -@test "dot setup folders still succeeds when a collision occurs" $collision_status -eq 0 -@test "the colliding target file is preserved byte-for-byte" (cat $HOME/doc/report.txt) = target-content -@test "the colliding legacy file is left in place, untouched" (cat $HOME/Documents/report.txt) = legacy-content -@test "the collision is reported" (string match -q '*report.txt*' -- $collision_output; echo $status) -eq 0 -@test "the legacy Documents folder is left in place due to the collision" -d $HOME/Documents -@test "a non-colliding file in the same folder is still merged" (cat $HOME/doc/notes.txt) = legacy-only -@test "the merged non-colliding file no longer sits in the legacy folder" (test -e $HOME/Documents/notes.txt; and echo yes; or echo no) = no - -# --- re-running after a collision was reported: the skipped file isn't -# lost, and the already-migrated file isn't moved again --- -dot setup folders >/dev/null 2>&1 - -@test "re-running after a collision still preserves the target file" (cat $HOME/doc/report.txt) = target-content -@test "re-running after a collision still leaves the legacy file in place" (cat $HOME/Documents/report.txt) = legacy-content -@test "re-running after a collision does not resurrect the already-migrated file in the legacy folder" (test -e $HOME/Documents/notes.txt; and echo yes; or echo no) = no - -# --- a collision on the nested Screenshots unit is skipped, reported, and -# leaves Pictures in place, even though Pictures also holds other -# content unrelated to the collision --- -set -gx HOME (mktemp -d) -dot init --url $remote >/dev/null 2>&1 -mkdir -p $HOME/.config/dot/commands/setup -cp $commands_dir/setup/setup.fish $HOME/.config/dot/commands/setup/setup.fish -cp $commands_dir/setup/folders.fish $HOME/.config/dot/commands/setup/folders.fish -mkdir -p $HOME/Pictures/Screenshots $HOME/pic/screenshots -echo legacy-shot >$HOME/Pictures/Screenshots/shot.png -echo target-shot >$HOME/pic/screenshots/shot.png -set -gx XDG_UPDATE_LOG (mktemp) - -set -l screenshots_collision_output (dot setup folders 2>&1) - -@test "a Screenshots collision preserves the existing target screenshot" (cat $HOME/pic/screenshots/shot.png) = target-shot -@test "a Screenshots collision leaves the legacy Screenshots folder in place" (cat $HOME/Pictures/Screenshots/shot.png) = legacy-shot -@test "the Screenshots collision is reported" (string match -q '*Screenshots*' -- $screenshots_collision_output; echo $status) -eq 0 -@test "Pictures itself is left in place due to the Screenshots collision" -d $HOME/Pictures - -# --- the same Screenshots-collision handling also holds when Pictures has -# nothing else in it besides the colliding Screenshots folder --- -set -gx HOME (mktemp -d) -dot init --url $remote >/dev/null 2>&1 -mkdir -p $HOME/.config/dot/commands/setup -cp $commands_dir/setup/setup.fish $HOME/.config/dot/commands/setup/setup.fish -cp $commands_dir/setup/folders.fish $HOME/.config/dot/commands/setup/folders.fish -mkdir -p $HOME/Pictures/Screenshots $HOME/pic/screenshots -echo target-shot >$HOME/pic/screenshots/shot.png -set -gx XDG_UPDATE_LOG (mktemp) - -set -l bare_screenshots_collision_output (dot setup folders 2>&1) -set -l bare_screenshots_collision_status $status - -@test "a Screenshots-only collision still succeeds" $bare_screenshots_collision_status -eq 0 -@test "a Screenshots-only collision preserves the existing target screenshot" (cat $HOME/pic/screenshots/shot.png) = target-shot -@test "a Screenshots-only collision leaves the empty legacy Screenshots folder in place" -d $HOME/Pictures/Screenshots -@test "a Screenshots-only collision leaves Pictures itself in place" -d $HOME/Pictures -@test "the Screenshots-only collision is reported" (string match -q '*Screenshots*' -- $bare_screenshots_collision_output; echo $status) -eq 0 - -# --- --dry-run reports what would move/skip without touching the -# filesystem: no mkdir, no mv/rmdir, no xdg-user-dirs-update --- -set -gx HOME (mktemp -d) -dot init --url $remote >/dev/null 2>&1 -mkdir -p $HOME/.config/dot/commands/setup -cp $commands_dir/setup/setup.fish $HOME/.config/dot/commands/setup/setup.fish -cp $commands_dir/setup/folders.fish $HOME/.config/dot/commands/setup/folders.fish -mkdir -p $HOME/Documents $HOME/Pictures/Screenshots -echo real-content >$HOME/Documents/report.txt -set -gx XDG_UPDATE_LOG (mktemp) - -set -l dryrun_output (dot setup folders --dry-run 2>&1) -set -l dryrun_status $status - -@test "dot setup folders --dry-run succeeds" $dryrun_status -eq 0 -@test "--dry-run reports the entry it would move" (string match -q '*Documents*' -- $dryrun_output; echo $status) -eq 0 -@test "--dry-run reports the Screenshots folder it would move" (string match -q '*Screenshots*' -- $dryrun_output; echo $status) -eq 0 -@test "--dry-run leaves the legacy Documents folder's content untouched" (cat $HOME/Documents/report.txt) = real-content -@test "--dry-run does not remove the legacy Documents folder" -d $HOME/Documents -@test "--dry-run does not create the short-named target folder" (test -e $HOME/doc; and echo yes; or echo no) = no -@test "--dry-run does not rename the nested Screenshots folder" -d $HOME/Pictures/Screenshots -@test "--dry-run never invokes xdg-user-dirs-update" (test -s $XDG_UPDATE_LOG; and echo yes; or echo no) = no - -# --- --dry-run also reports a would-be collision without touching -# either side, and doesn't move the non-colliding entry either --- -set -gx HOME (mktemp -d) -dot init --url $remote >/dev/null 2>&1 -mkdir -p $HOME/.config/dot/commands/setup -cp $commands_dir/setup/setup.fish $HOME/.config/dot/commands/setup/setup.fish -cp $commands_dir/setup/folders.fish $HOME/.config/dot/commands/setup/folders.fish -mkdir -p $HOME/Documents $HOME/doc -echo legacy-content >$HOME/Documents/report.txt -echo legacy-only >$HOME/Documents/notes.txt -echo target-content >$HOME/doc/report.txt -set -gx XDG_UPDATE_LOG (mktemp) - -set -l dryrun_collision_output (dot setup folders --dry-run 2>&1) - -@test "--dry-run reports the would-be collision" (string match -q '*report.txt*' -- $dryrun_collision_output; echo $status) -eq 0 -@test "--dry-run leaves the colliding target file untouched" (cat $HOME/doc/report.txt) = target-content -@test "--dry-run leaves the colliding legacy file untouched" (cat $HOME/Documents/report.txt) = legacy-content -@test "--dry-run does not move the non-colliding file either" -e $HOME/Documents/notes.txt - -# --- a legacy folder with nothing to move produces no --dry-run output --- -set -gx HOME (mktemp -d) -dot init --url $remote >/dev/null 2>&1 -mkdir -p $HOME/.config/dot/commands/setup -cp $commands_dir/setup/setup.fish $HOME/.config/dot/commands/setup/setup.fish -cp $commands_dir/setup/folders.fish $HOME/.config/dot/commands/setup/folders.fish -mkdir -p $HOME/Desktop -set -gx XDG_UPDATE_LOG (mktemp) - -set -l dryrun_empty_output (dot setup folders --dry-run 2>&1) - -@test "--dry-run is silent for a legacy folder with nothing to move" -z "$dryrun_empty_output" -@test "--dry-run leaves an empty legacy folder in place" -d $HOME/Desktop - -# --- the removed --yes flag now fails fast as an unknown option --- -set -gx HOME (mktemp -d) -dot init --url $remote >/dev/null 2>&1 -mkdir -p $HOME/.config/dot/commands/setup -cp $commands_dir/setup/setup.fish $HOME/.config/dot/commands/setup/setup.fish -cp $commands_dir/setup/folders.fish $HOME/.config/dot/commands/setup/folders.fish -set -gx XDG_UPDATE_LOG (mktemp) - -dot setup folders --yes >/dev/null 2>&1 -set -l old_yes_status $status - -@test "dot setup folders --yes now fails as an unknown option" $old_yes_status -ne 0 - -# --- help prints usage and makes no filesystem changes --- -set -gx HOME (mktemp -d) -dot init --url $remote >/dev/null 2>&1 -mkdir -p $HOME/.config/dot/commands/setup -cp $commands_dir/setup/setup.fish $HOME/.config/dot/commands/setup/setup.fish -cp $commands_dir/setup/folders.fish $HOME/.config/dot/commands/setup/folders.fish -mkdir -p $HOME/Documents $HOME/Desktop -set -gx XDG_UPDATE_LOG (mktemp) - -set -l setup_help_output (dot setup help) -set -l setup_help_status $status -set -l folders_help_output (dot setup folders help) -set -l folders_help_status $status -set -l xdg_called_for_help (test -s $XDG_UPDATE_LOG; and echo yes; or echo no) - -@test "dot setup help succeeds" $setup_help_status -eq 0 -@test "dot setup help mentions folders" (string match -q '*folders*' -- $setup_help_output; echo $status) -eq 0 -@test "dot setup folders help succeeds" $folders_help_status -eq 0 -@test "dot setup folders help mentions the short-name convention" (string match -q '*.desktop*' -- $folders_help_output; echo $status) -eq 0 -@test "dot setup folders help documents --dry-run" (string match -q '*--dry-run*' -- $folders_help_output; echo $status) -eq 0 -@test "neither help invocation ever calls xdg-user-dirs-update" $xdg_called_for_help = no -@test "dot setup help leaves the legacy Documents folder untouched" -d $HOME/Documents -@test "dot setup folders help leaves the legacy Desktop folder untouched" -d $HOME/Desktop -@test "help does not create any short-named target folder" (test -e $HOME/doc; and echo yes; or echo no) = no - -# --- dot help discovers dot setup --- -set -l help_with_setup (dot help) -@test "dot help lists the setup subcommand" (string match -q '*setup*' -- $help_with_setup; echo $status) -eq 0 - -# --- dot vpn --- -# nmcli is faked out via a PATH-prepended bin that logs each invocation and -# fails only when $NMCLI_FAIL is set, mirroring dot install's fake pacman. -set -l fake_bin_vpn (mktemp -d) -echo '#!/bin/sh -echo "$@" >>"$NMCLI_LOG" -if [ -n "$NMCLI_FAIL" ]; then - exit 1 -fi -exit 0' >$fake_bin_vpn/nmcli -chmod +x $fake_bin_vpn/nmcli -set -gx PATH $fake_bin_vpn $PATH - -set -gx HOME (mktemp -d) -dot init --url $remote >/dev/null 2>&1 -mkdir -p $HOME/.config/dot/commands -cp $commands_dir/vpn.fish $HOME/.config/dot/commands/vpn.fish -set -gx NMCLI_LOG (mktemp) - -dot vpn up >/dev/null 2>&1 -set -l vpn_up_status $status -set -l vpn_up_called (string match -q '*connection up UDM-PRO-Laptop*' -- (cat $NMCLI_LOG); and echo yes; or echo no) - -@test "dot vpn up succeeds" $vpn_up_status -eq 0 -@test "dot vpn up calls nmcli connection up UDM-PRO-Laptop" $vpn_up_called = yes - -set -gx NMCLI_LOG (mktemp) -dot vpn down >/dev/null 2>&1 -set -l vpn_down_status $status -set -l vpn_down_called (string match -q '*connection down UDM-PRO-Laptop*' -- (cat $NMCLI_LOG); and echo yes; or echo no) - -@test "dot vpn down succeeds" $vpn_down_status -eq 0 -@test "dot vpn down calls nmcli connection down UDM-PRO-Laptop" $vpn_down_called = yes - -# --- dot vpn help touches nmcli not at all --- -set -gx NMCLI_LOG (mktemp) -set -l vpn_help_output (dot vpn help) -set -l vpn_help_status $status -set -l nmcli_called_for_vpn_help (test -s $NMCLI_LOG; and echo yes; or echo no) - -@test "dot vpn help succeeds" $vpn_help_status -eq 0 -@test "dot vpn help mentions up" (string match -q '*up*' -- $vpn_help_output; echo $status) -eq 0 -@test "dot vpn help mentions down" (string match -q '*down*' -- $vpn_help_output; echo $status) -eq 0 -@test "dot vpn help never invokes nmcli" $nmcli_called_for_vpn_help = no - -# --- an unrecognized subcommand prints usage and fails, without calling nmcli --- -set -gx NMCLI_LOG (mktemp) -dot vpn bogus >/dev/null 2>&1 -set -l vpn_bogus_status $status -set -l nmcli_called_for_bogus (test -s $NMCLI_LOG; and echo yes; or echo no) - -@test "dot vpn with an unrecognized subcommand fails" $vpn_bogus_status -ne 0 -@test "an unrecognized dot vpn subcommand never invokes nmcli" $nmcli_called_for_bogus = no - -# --- bare `dot vpn` (no subcommand) also prints usage and fails --- -set -gx NMCLI_LOG (mktemp) -dot vpn >/dev/null 2>&1 -set -l vpn_bare_status $status - -@test "bare dot vpn fails" $vpn_bare_status -ne 0 - -# --- a failing nmcli call propagates its exit status --- -set -gx NMCLI_FAIL 1 -dot vpn up >/dev/null 2>&1 -set -l vpn_up_fail_status $status -set -e NMCLI_FAIL - -@test "dot vpn up fails when nmcli fails" $vpn_up_fail_status -ne 0 - -# --- dot help discovers dot vpn --- -set -l help_with_vpn (dot help) -@test "dot help lists the vpn subcommand" (string match -q '*vpn*' -- $help_with_vpn; echo $status) -eq 0 diff --git a/.config/dot/tests/fixtures/kcfg/kwin.kcfg b/.config/dot/tests/fixtures/kcfg/kwin.kcfg deleted file mode 100644 index 6bf2c17..0000000 --- a/.config/dot/tests/fixtures/kcfg/kwin.kcfg +++ /dev/null @@ -1,9 +0,0 @@ - - - - - - Normal - - - diff --git a/.config/dot/tests/fixtures/kcfg/testrc.kcfg b/.config/dot/tests/fixtures/kcfg/testrc.kcfg deleted file mode 100644 index cbaf05e..0000000 --- a/.config/dot/tests/fixtures/kcfg/testrc.kcfg +++ /dev/null @@ -1,15 +0,0 @@ - - - - - - Hello - - - AliasDefault - - - SpacedDefault - - - diff --git a/.config/dot/tests/fixtures/kcfg/unmapped.kcfg b/.config/dot/tests/fixtures/kcfg/unmapped.kcfg deleted file mode 100644 index 193fe08..0000000 --- a/.config/dot/tests/fixtures/kcfg/unmapped.kcfg +++ /dev/null @@ -1,9 +0,0 @@ - - - - - - Unreachable - - - diff --git a/.config/fish/completions/bun.fish b/.config/fish/completions/bun.fish deleted file mode 100644 index 6b2dc5b..0000000 --- a/.config/fish/completions/bun.fish +++ /dev/null @@ -1,196 +0,0 @@ -# This is terribly complicated -# It's because: -# 1. bun run has to have dynamic completions -# 2. there are global options -# 3. bun {install add remove} gets special options -# 4. I don't know how to write fish completions well -# Contributions very welcome!! - -function __fish__get_bun_bins - string split ' ' (bun getcompletes b) -end - -function __fish__get_bun_scripts - set -lx SHELL bash - set -lx MAX_DESCRIPTION_LEN 40 - string trim (string split '\n' (string split '\t' (bun getcompletes z))) -end - -function __fish__get_bun_packages - if test (commandline -ct) != "" - set -lx SHELL fish - string split ' ' (bun getcompletes a (commandline -ct)) - end -end - -function __history_completions - set -l tokens (commandline --current-process --tokenize) - history --prefix (commandline) | string replace -r \^$tokens[1]\\s\* "" | string replace -r \^$tokens[2]\\s\* "" | string split ' ' -end - -function __fish__get_bun_bun_js_files - string split ' ' (bun getcompletes j) -end - -set -l bun_install_boolean_flags yarn production optional development no-save dry-run force no-cache silent verbose global -set -l bun_install_boolean_flags_descriptions "Write a yarn.lock file (yarn v1)" "Don't install devDependencies" "Add dependency to optionalDependencies" "Add dependency to devDependencies" "Don't update package.json or save a lockfile" "Don't install anything" "Always request the latest versions from the registry & reinstall all dependencies" "Ignore manifest cache entirely" "Don't output anything" "Excessively verbose logging" "Use global folder" - -set -l bun_builtin_cmds_without_run dev create help bun upgrade discord install remove add update init pm x repl -set -l bun_builtin_cmds_accepting_flags create help bun upgrade discord run init link unlink pm x update - -function __bun_complete_bins_scripts --inherit-variable bun_builtin_cmds_without_run -d "Emit bun completions for bins and scripts" - # Do nothing if we already have a builtin subcommand, - # or any subcommand other than "run". - if __fish_seen_subcommand_from $bun_builtin_cmds_without_run - or not __fish_use_subcommand && not __fish_seen_subcommand_from run - return - end - # Do we already have a bin or script subcommand? - set -l bins (__fish__get_bun_bins) - if __fish_seen_subcommand_from $bins - return - end - # Scripts have descriptions appended with a tab separator. - # Strip off descriptions for the purposes of subcommand testing. - set -l scripts (__fish__get_bun_scripts) - if __fish_seen_subcommand_from (string split \t -f 1 -- $scripts) - return - end - # Emit scripts. - for script in $scripts - echo $script - end - # Emit binaries and JS files (but only if we're doing `bun run`). - if __fish_seen_subcommand_from run - for bin in $bins - echo "$bin"\t"package bin" - end - for file in (__fish__get_bun_bun_js_files) - echo "$file"\t"Bun.js" - end - end -end - - -# Clear existing completions -complete -e -c bun - -# Dynamically emit scripts and binaries -complete -c bun -f -a "(__bun_complete_bins_scripts)" - -# Complete flags if we have no subcommand or a flag-friendly one. -set -l flag_applies "__fish_use_subcommand; or __fish_seen_subcommand_from $bun_builtin_cmds_accepting_flags" -complete -c bun \ - -n $flag_applies --no-files -s 'u' -l 'origin' -r -d 'Server URL. Rewrites import paths' -complete -c bun \ - -n $flag_applies --no-files -s 'p' -l 'port' -r -d 'Port number to start server from' -complete -c bun \ - -n $flag_applies --no-files -s 'd' -l 'define' -r -d 'Substitute K:V while parsing, e.g. --define process.env.NODE_ENV:\"development\"' -complete -c bun \ - -n $flag_applies --no-files -s 'e' -l 'external' -r -d 'Exclude module from transpilation (can use * wildcards). ex: -e react' -complete -c bun \ - -n $flag_applies --no-files -l 'use' -r -d 'Use a framework (ex: next)' -complete -c bun \ - -n $flag_applies --no-files -l 'hot' -r -d 'Enable hot reloading in Bun\'s JavaScript runtime' - -# Complete dev and create as first subcommand. -complete -c bun \ - -n "__fish_use_subcommand" -a 'dev' -d 'Start dev server' -complete -c bun \ - -n "__fish_use_subcommand" -a 'create' -f -d 'Create a new project from a template' - -# Complete "next" and "react" if we've seen "create". -complete -c bun \ - -n "__fish_seen_subcommand_from create" -a 'next' -d 'new Next.js project' - -complete -c bun \ - -n "__fish_seen_subcommand_from create" -a 'react' -d 'new React project' - -# Complete "upgrade" as first subcommand. -complete -c bun \ - -n "__fish_use_subcommand" -a 'upgrade' -d 'Upgrade bun to the latest version' -x -# Complete "-h/--help" unconditionally. -complete -c bun \ - -s "h" -l "help" -d 'See all commands and flags' -x - -# Complete "-v/--version" if we have no subcommand. -complete -c bun \ - -n "not __fish_use_subcommand" -l "version" -s "v" -d 'Bun\'s version' -x - -# Complete additional subcommands. -complete -c bun \ - -n "__fish_use_subcommand" -a 'discord' -d 'Open bun\'s Discord server' -x - - -complete -c bun \ - -n "__fish_use_subcommand" -a 'bun' -d 'Generate a new bundle' - - -complete -c bun \ - -n "__fish_seen_subcommand_from bun" -F -d 'Bundle this' - -complete -c bun \ - -n "__fish_seen_subcommand_from create; and __fish_seen_subcommand_from react next" -F -d "Create in directory" - - -complete -c bun \ - -n "__fish_use_subcommand" -a 'init' -F -d 'Start an empty Bun project' - -complete -c bun \ - -n "__fish_use_subcommand" -a 'install' -f -d 'Install packages from package.json' - -complete -c bun \ - -n "__fish_use_subcommand" -a 'add' -F -d 'Add a package to package.json' - -complete -c bun \ - -n "__fish_use_subcommand" -a 'remove' -F -d 'Remove a package from package.json' - - -for i in (seq (count $bun_install_boolean_flags)) - complete -c bun \ - -n "__fish_seen_subcommand_from install add remove update" -l "$bun_install_boolean_flags[$i]" -d "$bun_install_boolean_flags_descriptions[$i]" -end - -complete -c bun \ - -n "__fish_seen_subcommand_from install add remove update" -l 'cwd' -d 'Change working directory' - -complete -c bun \ - -n "__fish_seen_subcommand_from install add remove update" -l 'cache-dir' -d 'Choose a cache directory (default: $HOME/.bun/install/cache)' - -complete -c bun \ - -n "__fish_seen_subcommand_from add" -d 'Popular' -a '(__fish__get_bun_packages)' - -complete -c bun \ - -n "__fish_seen_subcommand_from add" -d 'History' -a '(__history_completions)' - -complete -c bun \ - -n "__fish_seen_subcommand_from pm; and not __fish_seen_subcommand_from (__fish__get_bun_bins) (__fish__get_bun_scripts) cache;" -a 'bin ls cache hash hash-print hash-string' -f - -complete -c bun \ - -n "__fish_seen_subcommand_from pm; and __fish_seen_subcommand_from cache; and not __fish_seen_subcommand_from (__fish__get_bun_bins) (__fish__get_bun_scripts);" -a 'rm' -f - -# Add built-in subcommands with descriptions. -complete -c bun -n "__fish_use_subcommand" -a "create" -f -d "Create a new project from a template" -complete -c bun -n "__fish_use_subcommand" -a "build bun" --require-parameter -F -d "Transpile and bundle one or more files" -complete -c bun -n "__fish_use_subcommand" -a "upgrade" -d "Upgrade Bun" -complete -c bun -n "__fish_use_subcommand" -a "run" -d "Run a script or package binary" -complete -c bun -n "__fish_use_subcommand" -a "install" -d "Install dependencies from package.json" -f -complete -c bun -n "__fish_use_subcommand" -a "remove" -d "Remove a dependency from package.json" -f -complete -c bun -n "__fish_use_subcommand" -a "add" -d "Add a dependency to package.json" -f -complete -c bun -n "__fish_use_subcommand" -a "init" -d "Initialize a Bun project in this directory" -f -complete -c bun -n "__fish_use_subcommand" -a "link" -d "Register or link a local npm package" -f -complete -c bun -n "__fish_use_subcommand" -a "unlink" -d "Unregister a local npm package" -f -complete -c bun -n "__fish_use_subcommand" -a "pm" -d "Additional package management utilities" -f -complete -c bun -n "__fish_use_subcommand" -a "x" -d "Execute a package binary, installing if needed" -f -complete -c bun -n "__fish_use_subcommand" -a "outdated" -d "Display the latest versions of outdated dependencies" -f -complete -c bun -n "__fish_use_subcommand" -a "update" -d "Update dependencies to their latest versions" -f -complete -c bun -n "__fish_use_subcommand" -a "publish" -d "Publish your package from local to npm" -f -complete -c bun -n "__fish_use_subcommand" -a "repl" -d "Start a REPL session with Bun" -f -complete -c bun -n "__fish_seen_subcommand_from repl" -s "e" -l "eval" -r -d "Evaluate argument as a script, then exit" -f -complete -c bun -n "__fish_seen_subcommand_from repl" -s "p" -l "print" -r -d "Evaluate argument as a script, print the result, then exit" -f -complete -c bun -n "__fish_seen_subcommand_from repl" -s "r" -l "preload" -r -d "Import a module before other modules are loaded" -complete -c bun -n "__fish_seen_subcommand_from repl" -l "smol" -d "Use less memory, but run garbage collection more often" -f -complete -c bun -n "__fish_seen_subcommand_from repl" -s "c" -l "config" -r -d "Specify path to Bun config file" -complete -c bun -n "__fish_seen_subcommand_from repl" -l "cwd" -r -d "Absolute path to resolve files & entry points from" -complete -c bun -n "__fish_seen_subcommand_from repl" -l "env-file" -r -d "Load environment variables from the specified file(s)" -complete -c bun -n "__fish_seen_subcommand_from repl" -l "no-env-file" -d "Disable automatic loading of .env files" -f diff --git a/.config/fish/completions/dot.fish b/.config/fish/completions/dot.fish deleted file mode 100644 index d686b0b..0000000 --- a/.config/fish/completions/dot.fish +++ /dev/null @@ -1,34 +0,0 @@ -function __dot_custom_subcommands - echo init - echo help - path basename $HOME/.config/dot/commands/*.fish 2>/dev/null | path change-extension '' - - for d in $HOME/.config/dot/commands/*/ - test -d $d; or continue - set -l name (path basename $d) - test -f $d$name.fish; or continue - echo $name - end -end - -complete -c dot -n __fish_use_subcommand -a "(__dot_custom_subcommands)" - -# --- dot install --- -complete -c dot -n "__fish_seen_subcommand_from install; and not __fish_seen_argument -l restore" -l restore -d "reinstall every package from the saved list" -complete -c dot -n "__fish_seen_subcommand_from install; and not __fish_seen_argument -l no-sync" -l no-sync -d "skip the pacman -Sy database refresh" -complete -c dot -n "__fish_seen_subcommand_from install; and not __fish_seen_argument -l restore" -f -a "(__fish_print_pacman_packages)" - -# --- dot setup --- -complete -c dot -n "__fish_seen_subcommand_from setup; and not __fish_seen_subcommand_from folders help" -f -a folders -d "bring the 8 standard XDG user directories under the short-name convention" -complete -c dot -n "__fish_seen_subcommand_from setup; and not __fish_seen_subcommand_from folders help" -f -a help -d "show usage" -complete -c dot -n "__fish_seen_subcommand_from setup; and __fish_seen_subcommand_from folders" -f -a help -d "show usage" - -# --- dot kde --- -complete -c dot -n "__fish_seen_subcommand_from kde; and not __fish_seen_subcommand_from apply diff save help" -f -a apply -d "push manifest entries onto the live system" -complete -c dot -n "__fish_seen_subcommand_from kde; and not __fish_seen_subcommand_from apply diff save help" -f -a diff -d "scan for settings whose live value differs from its default" -complete -c dot -n "__fish_seen_subcommand_from kde; and not __fish_seen_subcommand_from apply diff save help" -f -a save -d "write live KDE settings into the manifest" -complete -c dot -n "__fish_seen_subcommand_from kde; and not __fish_seen_subcommand_from apply diff save help" -f -a help -d "show usage" -complete -c dot -n "__fish_seen_subcommand_from kde; and __fish_seen_subcommand_from apply diff save" -f -a help -d "show usage" -# Sourced live from the schema mapping table (real .kcfg files), not a -# hardcoded list -- same helper kde.py's own save/refresh logic builds from. -complete -c dot -n "__fish_seen_subcommand_from kde; and __fish_seen_subcommand_from save" -f -a "(python3 $HOME/.config/dot/commands/kde/kde.py complete 2>/dev/null)" diff --git a/.config/fish/completions/fishtape.fish b/.config/fish/completions/fishtape.fish deleted file mode 100644 index ad81efe..0000000 --- a/.config/fish/completions/fishtape.fish +++ /dev/null @@ -1,2 +0,0 @@ -complete --command fishtape --short v --long version --description "Print version" -complete --command fishtape --short h --long help --description "Print help" diff --git a/.config/fish/conf.d/aliases.fish b/.config/fish/conf.d/aliases.fish deleted file mode 100644 index a0e0c45..0000000 --- a/.config/fish/conf.d/aliases.fish +++ /dev/null @@ -1,4 +0,0 @@ -alias cp='cp -v' -alias vi=nvim -alias vim=nvim -alias tmx='tmux new-session -A -s' diff --git a/.config/fish/conf.d/env.fish b/.config/fish/conf.d/env.fish deleted file mode 100644 index ceeae7e..0000000 --- a/.config/fish/conf.d/env.fish +++ /dev/null @@ -1,4 +0,0 @@ -set -gx EDITOR nvim -set -x ANDROID_HOME $HOME/Android/Sdk -fish_add_path $ANDROID_HOME/platform-tools -fish_add_path $ANDROID_HOME/tools/bin diff --git a/.config/fish/conf.d/rustup.fish b/.config/fish/conf.d/rustup.fish deleted file mode 100644 index 36ff9b3..0000000 --- a/.config/fish/conf.d/rustup.fish +++ /dev/null @@ -1 +0,0 @@ -test -f "$HOME/.cargo/env.fish"; and source "$HOME/.cargo/env.fish" diff --git a/.config/fish/config.fish b/.config/fish/config.fish deleted file mode 100644 index f5ec738..0000000 --- a/.config/fish/config.fish +++ /dev/null @@ -1,15 +0,0 @@ -source /usr/share/cachyos-fish-config/cachyos-config.fish - -set -gx EDITOR nvim -set -gx VISUAL nvim - -# overwrite greeting -# potentially disabling fastfetch -#function fish_greeting -# # smth smth -#end - -# bun -set --export BUN_INSTALL "$HOME/.bun" -set --export PATH $BUN_INSTALL/bin $PATH - diff --git a/.config/fish/fish_plugins b/.config/fish/fish_plugins deleted file mode 100644 index 32526b8..0000000 --- a/.config/fish/fish_plugins +++ /dev/null @@ -1 +0,0 @@ -jorgebucaran/fishtape diff --git a/.config/fish/fish_variables b/.config/fish/fish_variables deleted file mode 100644 index 02a1500..0000000 --- a/.config/fish/fish_variables +++ /dev/null @@ -1,82 +0,0 @@ -# This file contains fish universal variable definitions. -# VERSION: 3.0 -SETUVAR __done_min_cmd_duration:10000 -SETUVAR __done_notification_urgency_level:low -SETUVAR __fish_initialized:4300 -SETUVAR _fisher_jorgebucaran_2F_fishtape_files:\x7e/\x2econfig/fish/functions/fishtape\x2efish\x1e\x7e/\x2econfig/fish/completions/fishtape\x2efish -SETUVAR _fisher_plugins:jorgebucaran/fishtape -SETUVAR _fisher_upgraded_to_4_4:\x1d -SETUVAR fish_user_paths:/home/alexion/\x2elocal/bin\x1e/home/alexion/Android/Sdk/platform\x2dtools -SETUVAR pure_begin_prompt_with_current_directory:true -SETUVAR pure_check_for_new_release:false -SETUVAR pure_color_at_sign:pure_color_mute -SETUVAR pure_color_aws_profile:pure_color_warning -SETUVAR pure_color_command_duration:pure_color_warning -SETUVAR pure_color_current_directory:pure_color_primary -SETUVAR pure_color_danger:red -SETUVAR pure_color_dark:black -SETUVAR pure_color_exit_status:pure_color_danger -SETUVAR pure_color_git_branch:pure_color_mute -SETUVAR pure_color_git_dirty:pure_color_mute -SETUVAR pure_color_git_stash:pure_color_info -SETUVAR pure_color_git_unpulled_commits:pure_color_info -SETUVAR pure_color_git_unpushed_commits:pure_color_info -SETUVAR pure_color_hostname:pure_color_mute -SETUVAR pure_color_info:cyan -SETUVAR pure_color_jobs:pure_color_normal -SETUVAR pure_color_k8s_context:pure_color_success -SETUVAR pure_color_k8s_namespace:pure_color_primary -SETUVAR pure_color_k8s_prefix:pure_color_info -SETUVAR pure_color_light:white -SETUVAR pure_color_mute:brblack -SETUVAR pure_color_nixdevshell_prefix:pure_color_info -SETUVAR pure_color_nixdevshell_symbol:pure_color_mute -SETUVAR pure_color_normal:normal -SETUVAR pure_color_prefix_root_prompt:pure_color_danger -SETUVAR pure_color_primary:blue -SETUVAR pure_color_prompt_on_error:pure_color_danger -SETUVAR pure_color_prompt_on_success:pure_color_success -SETUVAR pure_color_success:magenta -SETUVAR pure_color_system_time:pure_color_mute -SETUVAR pure_color_username_normal:pure_color_mute -SETUVAR pure_color_username_root:pure_color_light -SETUVAR pure_color_virtualenv:pure_color_mute -SETUVAR pure_color_warning:yellow -SETUVAR pure_convert_exit_status_to_signal:false -SETUVAR pure_enable_aws_profile:true -SETUVAR pure_enable_container_detection:true -SETUVAR pure_enable_git:true -SETUVAR pure_enable_k8s:false -SETUVAR pure_enable_nixdevshell:false -SETUVAR pure_enable_single_line_prompt:false -SETUVAR pure_enable_virtualenv:true -SETUVAR pure_reverse_prompt_symbol_in_vimode:true -SETUVAR pure_separate_prompt_on_error:false -SETUVAR pure_shorten_prompt_current_directory_length:0 -SETUVAR pure_shorten_window_title_current_directory_length:0 -SETUVAR pure_show_exit_status:false -SETUVAR pure_show_jobs:false -SETUVAR pure_show_numbered_git_indicator:false -SETUVAR pure_show_prefix_root_prompt:false -SETUVAR pure_show_subsecond_command_duration:false -SETUVAR pure_show_system_time:false -SETUVAR pure_symbol_aws_profile_prefix: -SETUVAR pure_symbol_container_prefix: -SETUVAR pure_symbol_exit_status_prefix:\x7c -SETUVAR pure_symbol_exit_status_separator:\x7c -SETUVAR pure_symbol_git_dirty:\x2a -SETUVAR pure_symbol_git_stash:\u2261 -SETUVAR pure_symbol_git_unpulled_commits:\u21e3 -SETUVAR pure_symbol_git_unpushed_commits:\u21e1 -SETUVAR pure_symbol_k8s_prefix:\u2638 -SETUVAR pure_symbol_nixdevshell_prefix:\u2744\ufe0f -SETUVAR pure_symbol_prefix_root_prompt:\x23 -SETUVAR pure_symbol_prompt:\u276f -SETUVAR pure_symbol_reverse_prompt:\u276e -SETUVAR pure_symbol_ssh_prefix: -SETUVAR pure_symbol_title_bar_separator:\x2d -SETUVAR pure_symbol_virtualenv_prefix: -SETUVAR pure_system_time_format:\x2b\x25T -SETUVAR pure_threshold_command_duration:5 -SETUVAR pure_truncate_prompt_current_directory_keeps:\x2d1 -SETUVAR pure_truncate_window_title_current_directory_keeps:\x2d1 diff --git a/.config/fish/functions/dot.fish b/.config/fish/functions/dot.fish deleted file mode 100644 index aea4c0d..0000000 --- a/.config/fish/functions/dot.fish +++ /dev/null @@ -1,130 +0,0 @@ -function dot --wraps=git --description 'Manage dotfiles via a bare repo checked out over $HOME' - set -l dotfiles_dir $HOME/.dotfiles - - if test "$argv[1]" = init - set -e argv[1] - __dot_init $dotfiles_dir $argv - return $status - end - - if test "$argv[1]" = help - __dot_help - return $status - end - - set -l commands_dir $HOME/.config/dot/commands - set -l command_file $commands_dir/$argv[1].fish - set -l nested_command_file $commands_dir/$argv[1]/$argv[1].fish - - if test -n "$argv[1]" - if test -f "$command_file" - source $command_file - _dot_$argv[1] $argv[2..-1] - return $status - else if test -f "$nested_command_file" - source $nested_command_file - _dot_$argv[1] $argv[2..-1] - return $status - end - end - - git --git-dir=$dotfiles_dir --work-tree=$HOME $argv -end - -# Kept inline (not a separate autoloaded function file) because this is the -# only subcommand that must work before the dotfiles repo has been cloned. -function __dot_init - set -l dotfiles_dir $argv[1] - set -e argv[1] - - argparse 'url=' -- $argv - or return 1 - - set -l url $_flag_url - test -n "$url"; or set url ssh://gitea@git.alexion.dev:2022/alexion/dotfiles.git - - if test -e $dotfiles_dir - echo "dot init: $dotfiles_dir already exists, refusing to re-initialize" >&2 - return 1 - end - - git clone --bare $url $dotfiles_dir - or begin - echo "dot init: failed to clone $url" >&2 - return 1 - end - - git --git-dir=$dotfiles_dir config status.showUntrackedFiles no - - set -l checkout_output (git --git-dir=$dotfiles_dir --work-tree=$HOME checkout 2>&1) - set -l checkout_status $status - - if test $checkout_status -ne 0 - set -l conflicts - set -l in_block 0 - - for line in $checkout_output - if test $in_block -eq 1 - if string match -rq '^\s' -- $line - set -a conflicts (string trim -- $line) - continue - else - set in_block 0 - end - end - - string match -q '*would be overwritten by checkout:*' -- $line - and set in_block 1 - end - - if test (count $conflicts) -eq 0 - echo "dot init: checkout failed and no recoverable conflicts were found:" >&2 - printf '%s\n' $checkout_output >&2 - return 1 - end - - set -l backup_dir $HOME/.dotfiles-backup/(date +%Y%m%dT%H%M%S) - for f in $conflicts - mkdir -p (path dirname $backup_dir/$f) - mv $HOME/$f $backup_dir/$f - echo "dot init: backed up ~/$f to $backup_dir/$f" - end - - git --git-dir=$dotfiles_dir --work-tree=$HOME checkout - or begin - echo "dot init: checkout still failing after backing up conflicts, aborting" >&2 - return 1 - end - end - - echo "dot init: bootstrapped $dotfiles_dir from $url" -end - -# The custom-subcommand glob is duplicated (not shared with -# completions/dot.fish) because fish only autoloads a function from a file -# named after that function; a shared helper would go undefined if `dot help` -# ran in a completion context before `dot` itself had ever been sourced. -function __dot_help - echo "dot: manage dotfiles via a bare repo checked out over \$HOME - -Commands: - init bootstrap the dotfiles repo on a new machine - help show this message" - - for f in $HOME/.config/dot/commands/*.fish - test -e $f; or continue - echo " "(path basename $f | path change-extension '') - end - - for d in $HOME/.config/dot/commands/*/ - test -d $d; or continue - set -l name (path basename $d) - test -f $d$name.fish; or continue - echo " $name" - end - - echo " -Run 'dot help' for flags on a specific command. - -Any other command is passed through to git (dot status, dot add, dot commit, dot push, ...)." -end diff --git a/.config/fish/functions/fishtape.fish b/.config/fish/functions/fishtape.fish deleted file mode 100644 index 7759f92..0000000 --- a/.config/fish/functions/fishtape.fish +++ /dev/null @@ -1,116 +0,0 @@ -function fishtape --description "Test scripts, functions, and plugins in Fish" - switch "$argv" - case -v --version - echo "fishtape, version 3.0.1" - case "" -h --help - echo "Usage: fishtape Run test files" - echo "Options:" - echo " -v or --version Print version" - echo " -h or --help Print this help message" - case \* - set --local files (realpath $argv) - - for file in $files - if test ! -f $file - echo "fishtape: Invalid file or file not found: \"$file\"" >&2 - return 1 - end - end - - set --local operators -{n,z,b,c,d,e,f,g,G,k,L,O,p,r,s,S,t,u,w,x} - set --local expectations \ - "a non-zero length string" \ - "a zero length string" \ - "a block device" \ - "a character device" \ - "a directory" \ - "an existing file" \ - "a regular file" \ - "a file with the set-group-ID bit set" \ - "a file with same group ID as the current user" \ - "a file with the sticky bit set" \ - "a symbolic link" \ - "a file owned by the current user" \ - "a named pipe" \ - "a file marked as readable" \ - "a file of size greater than zero" \ - "a socket" \ - "a terminal tty file descriptor" \ - "a file with the set-user-ID bit set" \ - "a file marked as writable" \ - "a file marked as executable" - - set --universal _fishtape_test_number 0 - set --universal _fishtape_test_passed 0 - set --universal _fishtape_test_failed 0 - - function @echo - echo "# $argv" - end - - function @test --argument-names name --inherit-variable operators --inherit-variable expectations - set --erase argv[1] - set --query argv[2] || set --append argv "" - - set _fishtape_test_number (math $_fishtape_test_number + 1) - - if test $argv - set _fishtape_test_passed (math $_fishtape_test_passed + 1) - - echo "ok $_fishtape_test_number $name" - else - if test $argv[1] = "!" - set operator "! " - set expected "not " - set --erase argv[1] - end - - if set --query argv[3] - set operator "$operator"$argv[2] - set expected (string escape -- $argv[3]) - set actual (string escape -- $argv[1]) - else - set operator "$operator"$argv[1] - set expected "$expected"$expectations[(contains --index -- $argv[1] $operators)] - set actual (string escape -- $argv[2]) - end - - set _fishtape_test_failed (math $_fishtape_test_failed + 1) - - status print-stack-trace | - string replace --filter --regex -- "\s+called on line (\d+) of file (.+)" '$2:$1' | - read --local at - - echo "not ok $_fishtape_test_number $name" - echo " ---" - echo " operator: $operator" - echo " expected: $expected" - echo " actual: $actual" - echo " at: $at" - echo " ..." - end - end - - echo TAP version 13 - - for file in $files - fish --init-command=(functions @echo | string collect) --init-command=(functions @test | string collect) $file - end - - echo - echo "1..$_fishtape_test_number" - echo "# pass $_fishtape_test_passed" - test $_fishtape_test_failed -eq 0 && - echo "# ok" || - echo "# fail $_fishtape_test_failed" - - functions --erase @echo @test - - set --local failed $_fishtape_test_failed - set --erase _fishtape_test_number - set --erase _fishtape_test_passed - set --erase _fishtape_test_failed - - test $failed -eq 0 - end -end diff --git a/.config/nvim/after/ftplugin/markdown.lua b/.config/nvim/after/ftplugin/markdown.lua deleted file mode 100644 index 2848685..0000000 --- a/.config/nvim/after/ftplugin/markdown.lua +++ /dev/null @@ -1 +0,0 @@ -vim.opt_local.conceallevel = 2 diff --git a/.config/nvim/init.lua b/.config/nvim/init.lua deleted file mode 100644 index 5c7f4ef..0000000 --- a/.config/nvim/init.lua +++ /dev/null @@ -1,3 +0,0 @@ -require("vim_options") -require("keys") -require("plugin") diff --git a/.config/nvim/lazy-lock.json b/.config/nvim/lazy-lock.json deleted file mode 100644 index 2ec59b6..0000000 --- a/.config/nvim/lazy-lock.json +++ /dev/null @@ -1,13 +0,0 @@ -{ - "diffview.nvim": { "branch": "main", "commit": "4516612fe98ff56ae0415a259ff6361a89419b0a" }, - "gitsigns.nvim": { "branch": "main", "commit": "eb60cc7b94c46005237fd34170d76f3a089a90aa" }, - "lazy.nvim": { "branch": "main", "commit": "306a05526ada86a7b30af95c5cc81ffba93fef97" }, - "neogit": { "branch": "master", "commit": "6fc2fa890bd2031ed999c074daab0fb4feff20a5" }, - "nord.nvim": { "branch": "main", "commit": "87394d4fc35c901bbe38326a78d31ab1ead826b6" }, - "nvim-treesitter": { "branch": "master", "commit": "cf12346a3414fa1b06af75c79faebe7f76df080a" }, - "oil.nvim": { "branch": "master", "commit": "b73018b75affd13fa38e2fc94ef753b465f770d7" }, - "plenary.nvim": { "branch": "master", "commit": "74b06c6c75e4eeb3108ec01852001636d85a932b" }, - "render-markdown.nvim": { "branch": "main", "commit": "f422cb5c6855f150e2ddcfaf44e7157b98b34f6a" }, - "snacks.nvim": { "branch": "main", "commit": "882c996cf28183f4d63640de0b4c02ec886d01f2" }, - "which-key.nvim": { "branch": "main", "commit": "3aab2147e74890957785941f0c1ad87d0a44c15a" } -} diff --git a/.config/nvim/lua/keys.lua b/.config/nvim/lua/keys.lua deleted file mode 100644 index 486b5da..0000000 --- a/.config/nvim/lua/keys.lua +++ /dev/null @@ -1,8 +0,0 @@ -local map = vim.keymap.set - -map("n", "", "h", { desc = "Move focus left" }) -map("n", "", "j", { desc = "Move focus down" }) -map("n", "", "k", { desc = "Move focus up" }) -map("n", "", "l", { desc = "Move focus right" }) - -map("n", "", "nohlsearch", { desc = "Clear search highlight" }) diff --git a/.config/nvim/lua/plugin.lua b/.config/nvim/lua/plugin.lua deleted file mode 100644 index d2eea07..0000000 --- a/.config/nvim/lua/plugin.lua +++ /dev/null @@ -1,23 +0,0 @@ -local lazypath = vim.fn.stdpath("data") .. "/lazy/lazy.nvim" -if not vim.uv.fs_stat(lazypath) then - local lazyrepo = "https://github.com/folke/lazy.nvim.git" - local out = vim.fn.system({ "git", "clone", "--filter=blob:none", "--branch=stable", lazyrepo, lazypath }) - if vim.v.shell_error ~= 0 then - vim.api.nvim_echo({ - { "Failed to clone lazy.nvim:\n", "ErrorMsg" }, - { out, "WarningMsg" }, - { "\nPress any key to exit..." }, - }, true, {}) - vim.fn.getchar() - os.exit(1) - end -end -vim.opt.rtp:prepend(lazypath) - -require("lazy").setup({ - spec = { - { import = "plugins" }, - }, - install = { colorscheme = { "nord" } }, - checker = { enabled = false }, -}) diff --git a/.config/nvim/lua/plugins/git.lua b/.config/nvim/lua/plugins/git.lua deleted file mode 100644 index 94d7889..0000000 --- a/.config/nvim/lua/plugins/git.lua +++ /dev/null @@ -1,36 +0,0 @@ -return { - { - "NeogitOrg/neogit", - dependencies = { - "nvim-lua/plenary.nvim", - "sindrets/diffview.nvim", - }, - keys = { - { - "g", - function() - require("gitsigns").toggle_current_line_blame(true) - require("neogit").open() - end, - desc = "Open git (Neogit)", - }, - }, - config = function() - require("neogit").setup() - vim.api.nvim_create_autocmd("BufUnload", { - callback = function(args) - if vim.bo[args.buf].filetype == "NeogitStatus" then - require("gitsigns").toggle_current_line_blame(false) - end - end, - }) - end, - }, - { - "lewis6991/gitsigns.nvim", - event = "BufWinEnter", - opts = { - current_line_blame = false, - }, - }, -} diff --git a/.config/nvim/lua/plugins/navigation.lua b/.config/nvim/lua/plugins/navigation.lua deleted file mode 100644 index efa86f9..0000000 --- a/.config/nvim/lua/plugins/navigation.lua +++ /dev/null @@ -1,27 +0,0 @@ -return { - { - "stevearc/oil.nvim", - lazy = false, - opts = { - view_options = { show_hidden = true }, - }, - keys = { - { "e", "Oil", desc = "Open file browser" }, - }, - }, - { - "folke/snacks.nvim", - priority = 1000, - lazy = false, - opts = { - picker = { enabled = true }, - notifier = { enabled = true }, - input = { enabled = true }, - }, - keys = { - { "f", function() require("snacks").picker.files() end, desc = "Find files" }, - { "s", function() require("snacks").picker.grep() end, desc = "Search text" }, - { "b", function() require("snacks").picker.buffers() end, desc = "Switch buffer" }, - }, - }, -} diff --git a/.config/nvim/lua/plugins/ui.lua b/.config/nvim/lua/plugins/ui.lua deleted file mode 100644 index 5127f0a..0000000 --- a/.config/nvim/lua/plugins/ui.lua +++ /dev/null @@ -1,55 +0,0 @@ -return { - { - "gbprod/nord.nvim", - lazy = false, - priority = 1000, - opts = { - transparent = true, - }, - config = function(_, opts) - require("nord").setup(opts) - vim.cmd.colorscheme("nord") - end, - }, - { - "MeanderingProgrammer/render-markdown.nvim", - ft = { "markdown" }, - dependencies = { "nvim-treesitter/nvim-treesitter" }, - opts = {}, - }, - { - "folke/which-key.nvim", - lazy = false, - config = true, - }, - { - "nvim-treesitter/nvim-treesitter", - branch = "master", - build = ":TSUpdate", - opts = { - ensure_installed = { - "markdown", - "markdown_inline", - "lua", - "bash", - "fish", - "rust", - "javascript", - "typescript", - "java", - "kotlin", - "c", - "cpp", - "html", - "css", - "python", - }, - auto_install = false, - highlight = { enable = true }, - indent = { enable = true }, - }, - config = function(_, opts) - require("nvim-treesitter.configs").setup(opts) - end, - }, -} diff --git a/.config/nvim/lua/vim_options.lua b/.config/nvim/lua/vim_options.lua deleted file mode 100644 index f8ee951..0000000 --- a/.config/nvim/lua/vim_options.lua +++ /dev/null @@ -1,29 +0,0 @@ -vim.g.mapleader = " " - -local opt = vim.opt - --- Clipboard: use neovim's built-in OSC 52 provider, no external binary needed. -vim.g.clipboard = "osc52" -opt.clipboard = "unnamedplus" - -opt.number = true -opt.relativenumber = true - -opt.shiftwidth = 2 -opt.tabstop = 2 -opt.expandtab = true - -opt.mouse = "a" - -opt.undofile = true - -opt.ignorecase = true -opt.smartcase = true - -opt.splitright = true -opt.splitbelow = true - -opt.wrap = false - -opt.scrolloff = 8 -opt.cursorline = true diff --git a/.config/tmux/tmux.conf b/.config/tmux/tmux.conf deleted file mode 100644 index c478c78..0000000 --- a/.config/tmux/tmux.conf +++ /dev/null @@ -1,74 +0,0 @@ -# Prefix: Ctrl-Space. Chosen over Ctrl-b (awkward reach) and Ctrl-a (collides -# with readline's beginning-of-line, which fights editing text in shells and -# in Claude Code's prompt). Verified clear of IME/KDE/Claude Code bindings. -unbind C-b -set -g prefix C-Space -bind C-Space send-prefix - -set -g mouse on -# OSC52 lets copy-mode selections land in the system clipboard via the -# terminal itself (Alacritty supports it) -- no wl-copy/xclip needed, and it -# still works over SSH later since the escape sequence travels with the data. -set -g set-clipboard on - -set -g mode-keys vi -set -g status-keys vi -bind -T copy-mode-vi v send -X begin-selection -bind -T copy-mode-vi y send -X copy-selection-and-cancel -bind -T copy-mode-vi MouseDragEnd1Pane send -X copy-selection-and-cancel - -# tmux's -h/-v split flags name the *arrangement*, not the divider line, which -# is backwards from how the divider looks -- so pick keys by what they draw: -# \ draws a side-by-side split (vertical line), - draws a stacked split -# (horizontal line). Unshifted versions of |/- since splitting is frequent. -unbind % -unbind '"' -bind \\ split-window -h -c "#{pane_current_path}" -bind - split-window -v -c "#{pane_current_path}" -bind c new-window -c "#{pane_current_path}" - -bind h select-pane -L -bind j select-pane -D -bind k select-pane -U -bind l select-pane -R - -set -g base-index 1 -setw -g pane-base-index 1 -set -g renumber-windows on - -bind r source-file ~/.config/tmux/tmux.conf \; display-message "tmux.conf reloaded" - -# True color passthrough. ",*" (rather than naming Alacritty's xterm-256color -# specifically) so this keeps working if the terminal emulator changes later. -set -g default-terminal "tmux-256color" -set -ag terminal-overrides ",*:RGB" - -# Default 500ms delay on Esc exists to disambiguate meta-key sequences; it -# reads as noticeable lag exiting insert mode in neovim, so drop it. -set -sg escape-time 10 - -set -g history-limit 10000 - -# Flag a background window in the status bar when its Claude Code session -# rings the terminal bell (permission prompt / task done while unfocused). -# bell-action=none stops tmux from ever passing the actual BEL through to -# Alacritty (no beep, no flash) -- monitor-bell's per-window tracking for the -# status-line highlight is independent of that and keeps working. -setw -g monitor-bell on -set -g bell-action none - -# Minimal status bar (session + window list only), styled to match the Nord -# theme already used in alacritty.toml. -set -g status-position bottom -set -g status-style "bg=#2E3440,fg=#D8DEE9" -set -g status-left " #S " -set -g status-left-length 20 -set -g status-right "" -setw -g window-status-current-style "bg=#88C0D0,fg=#2E3440,bold" -setw -g window-status-current-format " #I:#W " -setw -g window-status-format " #I:#W " -setw -g window-status-style "fg=#4C566A" -setw -g window-status-bell-style "bg=#BF616A,fg=#2E3440,bold" - -set -g pane-border-style "fg=#3B4252" -set -g pane-active-border-style "fg=#88C0D0" diff --git a/.config/user-dirs.dirs b/.config/user-dirs.dirs deleted file mode 100644 index 1b5a3ff..0000000 --- a/.config/user-dirs.dirs +++ /dev/null @@ -1,16 +0,0 @@ -# This file is written by xdg-user-dirs-update -# If you want to change or add directories, just edit the line you're -# interested in. All local changes will be retained on the next run. -# Format is XDG_xxx_DIR="$HOME/yyy", where yyy is a shell-escaped -# homedir-relative path, or XDG_xxx_DIR="/yyy", where /yyy is an -# absolute path. No other format is supported. -# -XDG_DESKTOP_DIR="$HOME/.desktop" -XDG_DOWNLOAD_DIR="$HOME/dwn" -XDG_TEMPLATES_DIR="$HOME/.ignoreme" -XDG_PUBLICSHARE_DIR="$HOME/.ignoreme" -XDG_DOCUMENTS_DIR="$HOME/doc" -XDG_MUSIC_DIR="$HOME/mus" -XDG_PICTURES_DIR="$HOME/pic" -XDG_VIDEOS_DIR="$HOME/vid" -XDG_PROJECTS_DIR="$HOME/wrk" diff --git a/.gitconfig b/.gitconfig deleted file mode 100644 index 0e25d32..0000000 --- a/.gitconfig +++ /dev/null @@ -1,3 +0,0 @@ -[user] - name = alexion - email = contact@alexion.dev diff --git a/.github/README.md b/.github/README.md deleted file mode 100644 index e3b6e98..0000000 --- a/.github/README.md +++ /dev/null @@ -1,40 +0,0 @@ -# dotfiles - -Dotfiles managed as a bare git repo checked out over `$HOME`, for machines -running CachyOS with KDE Plasma. - -## Bootstrapping a new machine - -```sh -mkdir -p ~/.config/fish/functions -curl -fsSL https://git.alexion.dev/alexion/dotfiles/raw/branch/main/.config/fish/functions/dot.fish \ - -o ~/.config/fish/functions/dot.fish -fish -c 'dot init' -``` - -## Commands - -| Command | Description | -| ----------------------- | ----------------------------------------------------------------------------------------- | -| `dot help` | Lists available commands. | -| `dot init` | Bootstraps the dotfiles repo on a new machine. | -| `dot install ` | Installs the given pacman packages and appends them to the tracked list (`~/.config/dot/packages/pacman`). | -| `dot install --restore` | Reinstalls every package from the tracked list. | -| `dot kde apply` | Pushes every manifest entry's declared value onto the live system. | -| `dot kde diff` | Reports every schema-backed setting whose live value differs from its default, tagged declared or undeclared. | -| `dot kde help` | Lists `dot kde`'s subcommands. | -| `dot kde save ` | Reads a KDE setting's current live value and declares it in the manifest (`~/.config/dot/kde-manifest`). | -| `dot kde save` | Refreshes every already-declared manifest entry's value from the live system. | -| `dot setup` | Runs every machine-setup task (currently just `folders`). | -| `dot setup folders` | Brings the 8 standard XDG user directories (`~/Desktop`, `~/Documents`, ...) under a fixed short-name convention (`~/.desktop`, `~/doc`, ...). | -| `dot vpn up` | Brings the `UDM-PRO-Laptop` WireGuard connection up via NetworkManager. | -| `dot vpn down` | Brings the `UDM-PRO-Laptop` WireGuard connection down. | -| `dot ` | Everything else is passed to `git`. | - -See [CLAUDE.md](../.config/dot/CLAUDE.md) for the `dot` tool's internal -architecture, bootstrap logic, subcommand dispatch, and test suite. - -## Keybindings - -See [keybindings.md](keybindings.md) for custom and useful default -keybindings across configured tools (currently: tmux). diff --git a/.github/keybindings.md b/.github/keybindings.md deleted file mode 100644 index 1a393bb..0000000 --- a/.github/keybindings.md +++ /dev/null @@ -1,42 +0,0 @@ -# Keybindings - -Quick reference for custom and useful default keybindings, so they don't have -to be re-discovered or looked up per tool. - -Comma-separated keys are pressed in sequence, not together. - -| Key | Context | Action | -| ----------------------------------------------------- | ------- | -------------------------------------------------- | -| `Ctrl` + `Space`, `\` | tmux | Split side-by-side, opens in current directory | -| `Ctrl` + `Space`, `-` | tmux | Split stacked, opens in current directory | -| `Ctrl` + `Space`, `h` / `j` / `k` / `l` | tmux | Move focus left / down / up / right | -| `Ctrl` + `Space`, `z` | tmux | Zoom/unzoom pane to fullscreen | -| `Ctrl` + `Space`, `o` | tmux | Cycle focus to next pane | -| `Ctrl` + `Space`, `x` | tmux | Kill current pane (asks to confirm) | -| `Ctrl` + `Space`, `Ctrl` + `Up`/`Down`/`Left`/`Right` | tmux | Resize pane | -| `Ctrl` + `Space`, `c` | tmux | New window, opens in current directory | -| `Ctrl` + `Space`, `0`-`9` | tmux | Jump to window by number | -| `Ctrl` + `Space`, `n` / `p` | tmux | Next / previous window | -| `Ctrl` + `Space`, `w` | tmux | Interactive window list | -| `Ctrl` + `Space`, `,` | tmux | Rename current window | -| `Ctrl` + `Space`, `&` | tmux | Kill current window (asks to confirm) | -| `Ctrl` + `Space`, `[` | tmux | Enter copy mode | -| `Ctrl` + `Space`, `]` | tmux | Paste most recent copy | -| `h` / `j` / `k` / `l` | tmux | Move cursor | -| `v` | tmux | Begin selection | -| `y` | tmux | Copy selection to system clipboard, exit copy mode | -| `/` / `?` | tmux | Search forward / backward | -| `q` | tmux | Exit copy mode | -| `Ctrl` + `Space`, `d` | tmux | Detach from session | -| `Ctrl` + `Space`, `$` | tmux | Rename session | -| `Ctrl` + `Space`, `s` | tmux | Interactive session list | -| `Ctrl` + `Space`, `(` / `)` | tmux | Switch to previous / next session | -| `Ctrl` + `Space`, `r` | tmux | Reload `tmux.conf` | -| `Ctrl` + `h` / `j` / `k` / `l` | neovim | Move focus between splits left / down / up / right | -| `Esc` | neovim | Clear search highlight | -| `Space`, `e` | neovim | Toggle file explorer (netrw) | -| `CapsLock` | KDE | Acts as `Esc` (`kxkbrc` `Options=caps:escape_shifted_capslock`) | -| `Shift` + `CapsLock` | KDE | Toggles Caps Lock | -| `Meta` + `X` | KDE | Lock Session (moved off `Meta+L`, tracked via `dot kde`) | -| `Meta` + `Shift` + `q` | KDE | Kill window | - diff --git a/.gitignore b/.gitignore index 14a0144..07f4331 100644 --- a/.gitignore +++ b/.gitignore @@ -1,9 +1 @@ -.dotfiles -.DS_Store -*.swp -*.swo -*~ -Thumbs.db -**/__pycache__ -.config/fish/conf.d/secrets.fish /reference/ -- 2.47.3 From 064971f6012eeb767c2df89bee74a1fb4bf89b89 Mon Sep 17 00:00:00 2001 From: alexion Date: Sun, 19 Jul 2026 08:24:06 -0400 Subject: [PATCH 30/32] docs: adopt Conventional Commits for commit messages Vendor the v1.0.0 specification as docs/conventional-commits.md and record the convention in CLAUDE.md: type and optional scope, where the scope names the module or host, and no Gitea-specific references, since this repository is mirrored to GitHub where issue and pull-request numbers resolve to unrelated things. --- CLAUDE.md | 3 ++ docs/conventional-commits.md | 57 ++++++++++++++++++++++++++++++++++++ 2 files changed, 60 insertions(+) create mode 100644 docs/conventional-commits.md diff --git a/CLAUDE.md b/CLAUDE.md index 3c99aad..bce171c 100644 --- a/CLAUDE.md +++ b/CLAUDE.md @@ -18,6 +18,9 @@ The domain model (Host, Module, Skeleton, Auto-loader, Enable convention, overla - 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. ## Gotchas diff --git a/docs/conventional-commits.md b/docs/conventional-commits.md new file mode 100644 index 0000000..42cd666 --- /dev/null +++ b/docs/conventional-commits.md @@ -0,0 +1,57 @@ +# Conventional Commits + +> Sourced from [conventionalcommits.org/en/v1.0.0](https://www.conventionalcommits.org/en/v1.0.0/) +> by the Conventional Commits authors, licensed under +> [CC BY 3.0](https://creativecommons.org/licenses/by/3.0/). + +--- + +## Summary + +The Conventional Commits specification is a lightweight convention on top of commit messages. +It provides an easy set of rules for creating an explicit commit history; which makes it easier to write automated tools on top of. +This convention dovetails with [SemVer](http://semver.org), by describing the features, fixes, and breaking changes made in commit messages. + +The commit message should be structured as follows: + +``` +[optional scope]: + +[optional body] + +[optional footer(s)] +``` + +The commit contains the following structural elements, to communicate intent to the consumers of your library: + +1. **fix:** a commit of the _type_ `fix` patches a bug in your codebase (this correlates with `PATCH` in Semantic Versioning). +2. **feat:** a commit of the _type_ `feat` introduces a new feature to the codebase (this correlates with `MINOR` in Semantic Versioning). +3. **BREAKING CHANGE:** a commit that has a footer with a token `BREAKING CHANGE:`, or appends a `!` after the type/scope, introduces a breaking API change (correlating with `MAJOR` in Semantic Versioning). A BREAKING CHANGE can be part of commits of any _type_. +4. _types_ other than `fix:` and `feat:` are allowed, for example `build:`, `chore:`, `ci:`, `docs:`, `style:`, `refactor:`, `perf:`, `test:`, and others. +5. _footers_ other than `BREAKING CHANGE: ` may be provided and follow a convention similar to [git trailer format](https://git-scm.com/docs/git-interpret-trailers). + +Additional types are not mandated by the Conventional Commits specification, and have no implicit effect in Semantic Versioning (unless they include a BREAKING CHANGE). +A scope may be provided to a commit's type, to provide additional contextual information and is contained within parenthesis, e.g., `feat(parser): add ability to parse arrays`. + +--- + +## Specification + +The key words "MUST", "MUST NOT", "REQUIRED", "SHALL", "SHALL NOT", "SHOULD", "SHOULD NOT", "RECOMMENDED", "MAY", and "OPTIONAL" in this document are to be interpreted as described in [RFC 2119](https://www.ietf.org/rfc/rfc2119.txt). + +1. Commits MUST be prefixed with a type, which consists of a noun, `feat`, `fix`, etc., followed by the OPTIONAL scope, OPTIONAL `!`, and REQUIRED terminal colon and space. +2. The type `feat` MUST be used when a commit adds a new feature to your application or library. +3. The type `fix` MUST be used when a commit represents a bug fix for your application. +4. A scope MAY be provided after a type. A scope MUST consist of a noun describing a section of the codebase surrounded by parenthesis, e.g., `fix(parser):`. +5. A description MUST immediately follow the colon and space after the type/scope prefix. The description is a short summary of the code changes, e.g., _fix: array parsing issue when multiple spaces were contained in string_. +6. A longer commit body MAY be provided after the short description, providing additional contextual information about the code changes. The body MUST begin one blank line after the description. +7. A commit body is free-form and MAY consist of any number of newline separated paragraphs. +8. One or more footers MAY be provided one blank line after the body. Each footer MUST consist of a word token, followed by either a `:` or `#` separator, followed by a string value (this is inspired by the [git trailer convention](https://git-scm.com/docs/git-interpret-trailers)). +9. A footer's token MUST use `-` in place of whitespace characters, e.g., `Acked-by` (this helps differentiate the footer section from a multi-paragraph body). An exception is made for `BREAKING CHANGE`, which MAY also be used as a token. +10. A footer's value MAY contain spaces and newlines, and parsing MUST terminate when the next valid footer token/separator pair is observed. +11. Breaking changes MUST be indicated in the type/scope prefix of a commit, or as an entry in the footer section. +12. If included as a footer, a breaking change MUST consist of the uppercase text `BREAKING CHANGE`, followed by a colon, space, and description, e.g., _BREAKING CHANGE: environment variables now take precedence over config files_. +13. If included in the type/scope prefix, breaking changes MUST be indicated by a `!` immediately before the `:`. If `!` is used, `BREAKING CHANGE:` MAY be omitted from the footer section, and the commit description SHALL be used to describe the breaking change. +14. Types other than `feat` and `fix` MAY be used in your commit messages, e.g., _docs: correct spelling of CHANGELOG_. +15. The units of information that make up Conventional Commits MUST NOT be treated as case sensitive by implementors, with the exception of BREAKING CHANGE which MUST be uppercase. +16. BREAKING-CHANGE MUST be synonymous with BREAKING CHANGE, when used as a token in a footer. -- 2.47.3 From bdb6f01934ae190d0af5cbe7d052c54f92a21ac0 Mon Sep 17 00:00:00 2001 From: alexion Date: Sun, 19 Jul 2026 08:28:34 -0400 Subject: [PATCH 31/32] docs: standardise on gitea-axi as the Gitea CLI The Gotchas entry named `tea` as the CLI for issue and pull-request work. `gitea-axi` supersedes it; `tea` stays installed only to hold the `axi` login that `gitea-axi` authenticates from. --- CLAUDE.md | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/CLAUDE.md b/CLAUDE.md index bce171c..b60019f 100644 --- a/CLAUDE.md +++ b/CLAUDE.md @@ -37,7 +37,9 @@ The domain model (Host, Module, Skeleton, Auto-loader, Enable convention, overla - The primary build/verify seam for any Host is `nix flake check`, which builds `checks.x86_64-linux.` (the system toplevel); cheap targeted checks use `nix eval .#nixosConfigurations..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`); the forge CLI is `tea` (login `axi`), and `gh` is not installed. +- The remote is self-hosted Gitea (`git.alexion.dev`), driven with `gitea-axi` rather than `tea`; `gh` is not installed. + `gitea-axi` resolves the repository from the `origin` remote and takes credentials from the `axi` tea login, so both are implicit inside a checkout. + `tea` remains installed only as that credential source. - 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`. - nixpkgs `vimPlugins.nvim-treesitter` tracks the rewritten `main` branch: there is no `require("nvim-treesitter.configs").setup{ensure_installed,highlight,indent}`. Under nixvim, use `plugins.treesitter` with `highlight.enable`/`indent.enable` and `grammarPackages = with config.programs.nixvim.plugins.treesitter.package.builtGrammars; [ ... ]` — the module's own `package.builtGrammars`, **not** `pkgs.vimPlugins.nvim-treesitter.*` (whose query files can mismatch). The module targets the main branch and enables features via neovim-native APIs (`vim.treesitter.start()`, `require'nvim-treesitter'.indentexpr()`). - Neovim is configured via **nixvim** (flake input `nixvim`, consumed as `inputs.nixvim.homeModules.nixvim` added to `home-manager.sharedModules`, config under `home-manager.users..programs.nixvim`). `nixvim.inputs.nixpkgs.follows = "nixpkgs"` is set; nixvim then emits a benign eval warning that its pinned nixpkgs differs from the followed one — builds and runs fine, do not "fix" it by dropping the follows. -- 2.47.3 From 7809e079e34196990eeef52cf5e2c30fccc68fc3 Mon Sep 17 00:00:00 2001 From: alexion Date: Sun, 19 Jul 2026 08:34:22 -0400 Subject: [PATCH 32/32] docs(claude-code): teach the skill-management skills the Nix layout ~/.claude/skills is generated by home-manager: the directories are real but every leaf file is a read-only symlink into the store. The skills that author and install skills assumed it was an ordinary writable tree. - craft-skill: personal skills are authored in modules/claude-code/skills and applied by a rebuild, never edited under ~/.claude/skills; writing there succeeds silently and strands the skill outside the repo. - setup-skills, update-skills: copy out of the library with cp -rL and chmod -R u+w. A plain cp -r copies the symlinks, committing store paths into the project, and dereferenced files keep the store's read-only mode. - craft-skill also staged through `dot add`, a fish function this repo no longer carries; plain git add replaces it. --- CLAUDE.md | 4 ++++ modules/claude-code/skills/craft-skill/SKILL.md | 6 +++--- modules/claude-code/skills/setup-skills/SKILL.md | 9 ++++++++- modules/claude-code/skills/update-skills/SKILL.md | 7 +++++-- 4 files changed, 20 insertions(+), 6 deletions(-) diff --git a/CLAUDE.md b/CLAUDE.md index b60019f..ddc308c 100644 --- a/CLAUDE.md +++ b/CLAUDE.md @@ -40,6 +40,10 @@ The domain model (Host, Module, Skeleton, Auto-loader, Enable convention, overla - The remote is self-hosted Gitea (`git.alexion.dev`), driven with `gitea-axi` rather than `tea`; `gh` is not installed. `gitea-axi` resolves the repository from the `origin` remote and takes credentials from the `axi` tea login, so both are implicit inside a checkout. `tea` remains installed only as that credential source. +- `~/.claude/skills` is generated by home-manager with `recursive = true`, so the directories are real and writable but every leaf file is a read-only symlink into the store. + Editing a skill in place fails; its source is `modules/claude-code/skills//` here, applied by a rebuild. + Creating a new file under `~/.claude/skills/` succeeds silently and is the trap — it stays outside the repo and reaches no other machine. + Copying out of that tree needs `cp -rL` plus `chmod -R u+w`: a plain `cp -r` copies the symlinks, putting store paths into the destination, and dereferenced files keep the store's read-only mode. - 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`. - nixpkgs `vimPlugins.nvim-treesitter` tracks the rewritten `main` branch: there is no `require("nvim-treesitter.configs").setup{ensure_installed,highlight,indent}`. Under nixvim, use `plugins.treesitter` with `highlight.enable`/`indent.enable` and `grammarPackages = with config.programs.nixvim.plugins.treesitter.package.builtGrammars; [ ... ]` — the module's own `package.builtGrammars`, **not** `pkgs.vimPlugins.nvim-treesitter.*` (whose query files can mismatch). The module targets the main branch and enables features via neovim-native APIs (`vim.treesitter.start()`, `require'nvim-treesitter'.indentexpr()`). - Neovim is configured via **nixvim** (flake input `nixvim`, consumed as `inputs.nixvim.homeModules.nixvim` added to `home-manager.sharedModules`, config under `home-manager.users..programs.nixvim`). `nixvim.inputs.nixpkgs.follows = "nixpkgs"` is set; nixvim then emits a benign eval warning that its pinned nixpkgs differs from the followed one — builds and runs fine, do not "fix" it by dropping the follows. diff --git a/modules/claude-code/skills/craft-skill/SKILL.md b/modules/claude-code/skills/craft-skill/SKILL.md index 7b7f78c..98d78f9 100644 --- a/modules/claude-code/skills/craft-skill/SKILL.md +++ b/modules/claude-code/skills/craft-skill/SKILL.md @@ -23,11 +23,11 @@ If the request describes a new workflow, capability, or repeated manual process - Is there already a word — in your prompts, docs, or codebase — that names this behavior? Reach for that **leading word** before coining one. Done when every axis above has an answer, or the user says to just draft something and iterate. -2. **Write the draft.** First decide where it lives: project-local `.claude/skills/` if the workflow is tied to this one repo, `~/.claude/skills/` if it's general-purpose across projects. Then follow the **information hierarchy**: steps for what the agent does in order, in-file **reference** for facts every branch needs, and disclose the rest behind a pointer — to a sibling file, or to the existing project docs identified in step 1 rather than restating them. Done when every branch from step 1 has somewhere to live, and no sentence fails the no-op test in isolation (see `No-Op` in GLOSSARY.md). +2. **Write the draft.** First decide where it lives: project-local `.claude/skills/` if the workflow is tied to this one repo, the personal set if it's general-purpose across projects. The personal set is not authored in `~/.claude/skills/` — that tree is generated, and every file under it is a read-only symlink into the Nix store. Write it in the dotfiles repo at `modules/claude-code/skills//` and rebuild to make it live. Creating files directly under `~/.claude/skills/` looks like it works, because the directories themselves are writable, but the result is untracked by the repo and reaches no other machine. Then follow the **information hierarchy**: steps for what the agent does in order, in-file **reference** for facts every branch needs, and disclose the rest behind a pointer — to a sibling file, or to the existing project docs identified in step 1 rather than restating them. Done when every branch from step 1 has somewhere to live, and no sentence fails the no-op test in isolation (see `No-Op` in GLOSSARY.md). ## Audit an existing skill -1. **Locate it.** Check the current project's `.claude/skills/`, then `~/.claude/skills/`, then `~/.claude/skills/library/`, in that order; ask if the name is ambiguous across locations. If it's tracked in a project's `skills-lock.yaml`, mention that editing it here will make it read as locally-customized to `update-skills` — confirm that's actually intended rather than editing the library source. +1. **Locate it.** Check the current project's `.claude/skills/`, then `~/.claude/skills/`, then `~/.claude/skills/library/`, in that order; ask if the name is ambiguous across locations. A hit under `~/.claude/skills/` is a read-only symlink and cannot be edited in place: its source is the dotfiles repo, at `modules/claude-code/skills//` for a personal skill or `modules/claude-code/skills/library//` for a library one. Edit there and rebuild. If it's tracked in a project's `skills-lock.yaml`, mention that editing it here will make it read as locally-customized to `update-skills` — confirm that's actually intended rather than editing the library source. 2. **Apply the checklist.** Read the skill and its disclosed files, then check each against GLOSSARY.md, quoting the offending line for anything that fails: - **Premature completion** — is each completion criterion checkable, and does it demand what the step actually needs? @@ -44,6 +44,6 @@ If the request describes a new workflow, capability, or repeated manual process 1. Propose one realistic test prompt — reflecting the trigger phrasing gathered (draft) or the skill's existing purpose (audit) — and get it confirmed or adjusted before spending a run on it. 2. Spawn one subagent: give it the skill's path and the confirmed prompt, have it attempt the task using the skill, and report back what happened — including anywhere it hesitated, misread the skill, or did something unexpected. 3. Re-read the draft/rewrite against GLOSSARY.md's failure modes in light of that run, and fix whatever either pass turned up. If the fix is substantial, repeat from step 1; otherwise it's done. -4. Stage the specific changed or created paths — one path per file, never a wildcard — with the host project's own staging convention: plain `git add ` normally, or e.g. `dot add ` in this dotfiles setup (wrap as `fish -c "dot add "` if the invoking shell isn't fish — `dot` is a fish function, not a binary on `$PATH`). Do not commit; that's left to the user. +4. Stage the specific changed or created paths — one path per file, never a wildcard — with `git add `. Do not commit; that's left to the user. Done when the subagent's run succeeded without confusion on the confirmed prompt, the checklist raised nothing outstanding, and every changed path is staged. diff --git a/modules/claude-code/skills/setup-skills/SKILL.md b/modules/claude-code/skills/setup-skills/SKILL.md index 74d924e..a1aeb10 100644 --- a/modules/claude-code/skills/setup-skills/SKILL.md +++ b/modules/claude-code/skills/setup-skills/SKILL.md @@ -10,6 +10,12 @@ the current project's `.claude/skills/`, tracked in Only ever adds — checking already-installed skills for updates is [`update-skills`](../update-skills/SKILL.md)'s job, not this one's. +The library is a tree of read-only symlinks into the Nix store, so every +copy out of it must dereference (`cp -rL`) and then restore write +permission (`chmod -R u+w`). A plain `cp -r` copies the symlinks +themselves, putting store paths into the project that break on any other +machine. + ## Steps 1. Read `.claude/skills-lock.yaml` in the current project, if it exists. @@ -35,7 +41,8 @@ Only ever adds — checking already-installed skills for updates is skill already lives there and isn't tracked — remove or rename it first if they want the library version). - Otherwise, copy `~/.claude/skills/library//` to - `.claude/skills//` in the project, run + `.claude/skills//` in the project with + `cp -rL` followed by `chmod -R u+w`, run `~/.claude/skills/setup-skills/hash-dir.sh .claude/skills/`, and append `{name, hash: }` to `.claude/skills-lock.yaml` (create the file, an empty YAML list, if it doesn't exist yet). diff --git a/modules/claude-code/skills/update-skills/SKILL.md b/modules/claude-code/skills/update-skills/SKILL.md index 2d11a89..b0a17b3 100644 --- a/modules/claude-code/skills/update-skills/SKILL.md +++ b/modules/claude-code/skills/update-skills/SKILL.md @@ -36,8 +36,11 @@ skill that isn't already there — that's already included an explicit go-ahead argument (e.g. `-y`, `yes`), in which case apply them without asking. Applying means: delete `.claude/skills//` entirely and copy - `~/.claude/skills/library//` in its place, so no file the project - copy had but the library no longer has can survive — then recompute its + `~/.claude/skills/library//` in its place with `cp -rL` followed + by `chmod -R u+w` (the library is read-only symlinks into the Nix store; + a plain `cp -r` would put store paths into the project), so no file the + project copy had but the library no longer has can survive — then + recompute its hash and overwrite that entry's `hash` in `.claude/skills-lock.yaml` in place. -- 2.47.3