From c4d13f5dca5d6eec34e4be85f48c05291707396e Mon Sep 17 00:00:00 2001 From: Myles Prather Date: Sun, 28 Jun 2026 14:19:08 -0500 Subject: [PATCH] Add @shared-all synthetic group (@shared + non-env optionals) @shared excludes optional packages, and `all`/@engineering-loadout never pull them either, so there was no single name for "the full shared/read-only tree including optionals." Add @shared-all: every non-group package whose kind \!= env, regardless of `optional` -- i.e. @shared plus surfer, cicwave, rust, rust-crate-store. env-cargo is the only env-kind optional, so it stays on the @envs side and is NOT in @shared-all (which is disjoint from @envs by construction). Verified: @shared=124, @shared-all=128, @shared-all == @shared | non-env-optionals. Surfaces in `list --groups`, `info @shared-all`, and the regenerated bash completion via _SYNTHETIC_GROUPS, same as @shared / @envs. Co-Authored-By: Claude Opus 4.8 --- CLAUDE.md | 2 +- envs/bash/global/completions/loadout.bash | 2 +- loadout_main.py | 11 +++++++++-- 3 files changed, 11 insertions(+), 4 deletions(-) diff --git a/CLAUDE.md b/CLAUDE.md index 0d5ec474..bd61c2cc 100644 --- a/CLAUDE.md +++ b/CLAUDE.md @@ -221,7 +221,7 @@ When installer runs, `resolve_tool_selection(args, registry)` performs: 5. Walk soft `recommends` -- silently drop skipped/unknown. 6. Filter by current platform (`linux`/`macos`/`windows`). -Group expansion (`expand_groups`) recursive, cycle-detected. Synthetic groups expand at runtime (not stored in `packages.json`): `@shared` -> every non-group, **non-`optional`** package whose `kind != env` (everything for a shared/read-only tree); `@envs` -> its complement (every `kind == env` config bundle). `all` is also accepted as a name and expands to every non-group, non-`optional` package. **`optional: true` is the only gating field** (there is no `default` field): packages so flagged are skipped by the `all` / `@shared` / `@engineering-loadout` sweeps and install only when named explicitly or pulled by a group that lists them in `members` -- e.g. `surfer` (name-only) and the `@rust` trio (`rust`, `rust-crate-store`, `env-cargo`, reachable via `@rust`). Tools and env config bundles are **fully decoupled** -- no cross-`recommends` in either direction (a tool never recommends its env bundle, an env bundle never recommends its tool) -- so `./loadout install @shared --dest-dir ...` (shared tree) and `./loadout install @envs` (per-user config) are clean complements needing no `--skip`/`--no-deps`. Both synthetic groups surface in `list --groups` and `describe <@group>` (driven by `_SYNTHETIC_GROUPS`) despite not being registry entries. +Group expansion (`expand_groups`) recursive, cycle-detected. Synthetic groups expand at runtime (not stored in `packages.json`): `@shared` -> every non-group, **non-`optional`** package whose `kind != env` (everything for a shared/read-only tree); `@shared-all` -> the same but with optionals folded back in (every non-group `kind != env` package regardless of `optional` -- the full shared tree incl. `surfer`/`cicwave`/`rust`/`rust-crate-store` in one name); `@envs` -> the complement of `@shared` (every `kind == env` config bundle). `all` is also accepted as a name and expands to every non-group, non-`optional` package. **`optional: true` is the only gating field** (there is no `default` field): packages so flagged are skipped by the `all` / `@shared` / `@engineering-loadout` sweeps and install only when named explicitly, via `@shared-all`, or pulled by a group that lists them in `members` -- e.g. `surfer` (name-only) and the `@rust` trio (`rust`, `rust-crate-store`, `env-cargo`, reachable via `@rust`; note `env-cargo` is `kind == env` so it stays on the `@envs` side, NOT in `@shared-all`). Tools and env config bundles are **fully decoupled** -- no cross-`recommends` in either direction (a tool never recommends its env bundle, an env bundle never recommends its tool) -- so `./loadout install @shared --dest-dir ...` (shared tree) and `./loadout install @envs` (per-user config) are clean complements needing no `--skip`/`--no-deps`. All three synthetic groups surface in `list --groups` and `describe <@group>` (driven by `_SYNTHETIC_GROUPS`) despite not being registry entries. ### Subcommands diff --git a/envs/bash/global/completions/loadout.bash b/envs/bash/global/completions/loadout.bash index de9e0a12..493bcc35 100644 --- a/envs/bash/global/completions/loadout.bash +++ b/envs/bash/global/completions/loadout.bash @@ -21,7 +21,7 @@ _loadout_complete() { local verbs="install reinstall upgrade update list search info describe resolve doctor snapshot clean completion" local pkgs="agent-deck amux bash bash-docs bat biome broot btm btop bzip2 choose cicwave clang cloc dasel delta duf dust env-autohotkey env-bash env-cargo env-editorconfig env-helix env-nvim env-pip env-powershell env-starship env-tmux env-vim env-wezterm env-zsh expect eza fd fio firefox fish flameshot font-cascadiacode font-dejavusansmono font-envycoder font-firacode font-hack font-inconsolata font-iosevkaterm font-jetbrainsmono font-meslolg font-robotomono font-sourcecodepro font-ubuntumono fresh fzf glow gnu-coreutils gnuplot gobject-typelibs gocheat gopls gping gui_libs gvim htop hx hyperfine jq jupyterlab just keyb lazygit lefdef-tools liberty-filter liberty-tools llvm-bolt lua-language-server mate-terminal meld mesa3d_libs micro miller modules ncdu nedit-ng ngspice ninja nodejs numr nvim nvim-catalog-plugins nvim-qt octave p7zip patchelf pdftotext pigz portable-python procs pv pygwalker pyright rg rsync ruff rust rust-analyzer rust-crate-store scc sd shellcheck shfmt st starship stylua surfer tcl text-serdes time-plot tk tkdiff tldr tldr-data tmux tmux-path-store tokei tree-sitter treesitter-parsers ty urxvt uv vcd-toggle-profiler vim visidata wezterm xsel xterm yank yara yazi yq zellij zoxide zsh" - local groups="@all-environments @compression @core-cli @core-environments @data-bundles @dev-tools @editor-cli @editor-gui @editor-suite @engineering-loadout @envs @fonts-all @fonts-default @gui-suite @python-stack @python-tools-extra @rust @scientific @security @shared @shells-optional @system-monitor all" + local groups="@all-environments @compression @core-cli @core-environments @data-bundles @dev-tools @editor-cli @editor-gui @editor-suite @engineering-loadout @envs @fonts-all @fonts-default @gui-suite @python-stack @python-tools-extra @rust @scientific @security @shared @shared-all @shells-optional @system-monitor all" local tags="agent automation benchmark browser build cache clipboard compat compiler compress core data def dev diff disk docs document eda editor elf env file font format git gobject gui hotkeys hpc javascript lang lef liberty library lint llvm lsp lua markdown math monitor multiplexer nav network notebook nvim opengl optimizer parser pkg plot profiler prompt python reference runtime rust scientific screenshot scripting search security shell simulator spice sync system terminal text tmux toolchain tui vcd viewer visualization waveform" # Option that takes an argument -> complete the argument, not a new word. diff --git a/loadout_main.py b/loadout_main.py index baf387c7..3ec390f1 100755 --- a/loadout_main.py +++ b/loadout_main.py @@ -239,6 +239,7 @@ def _parse_namelist(raw): # `list --groups` and `describe`. _SYNTHETIC_GROUPS = { "@shared": "Every non-env, non-optional package -- install set for a shared/read-only tree.", + "@shared-all": "@shared plus every non-env optional package (surfer, cicwave, rust, ...).", "@envs": "Every per-user env config bundle (the complement of @shared).", } @@ -252,8 +253,10 @@ def expand_groups(names, registry, _stack=None): `--only @shared --skip @envs` to keep env recommends out of a shared tree. `all` (and the @shared / @engineering-loadout sweeps that build on it) skip packages flagged "optional": true -- those install only when named explicitly - or pulled in by a group that lists them (e.g. surfer, the @rust trio). Cycles - in the group graph raise ResolverError. + or pulled in by a group that lists them (e.g. surfer, the @rust trio). + @shared-all is @shared with those optionals folded back in (every non-env + package regardless of "optional") -- the full shared/read-only tree in one + name. Cycles in the group graph raise ResolverError. Unknown package names produce a warning and are dropped. """ if _stack is None: @@ -270,6 +273,10 @@ def expand_groups(names, registry, _stack=None): if not n.startswith("@") and e.get("kind") != "env" and not e.get("optional") } continue + if name == "@shared-all": + # @shared but optionals INCLUDED -- the full shared/read-only tree. + out |= {n for n, e in registry.items() if not n.startswith("@") and e.get("kind") != "env"} + continue if name == "@envs": out |= {n for n, e in registry.items() if not n.startswith("@") and e.get("kind") == "env"} continue