Files
skills/.claude/tasks/0002-global-placement-home-manager.md
alexion b0a763e5ce feat: add global-placement home-manager module (task 0002)
Expose homeModules.default, a home-manager module whose programs.agents.skills
option places operator-selected skill derivations into Claude Code's skills
directory, one recursive home.file per skill, gated on programs.claude-code.enable
and sourcing configDir from the claude-code module. Add a nix flake check that
builds the home-files tree under several operator configurations and asserts
placement, recursion, the enable gate, configDir sourcing, and coexistence with
an operator's own skills.
2026-07-22 17:18:05 -04:00

4.6 KiB

spec, blocked-by
spec blocked-by
nix-skill-packaging 0001-content-tier-skill-packaging

What to build

The global (every-project) placement output: a home-manager module that installs an operator's selected skills into ~/.claude/skills/, plus the check that proves its composition.

Exposed as homeModules.default. The operator-facing option is programs.agents.skills, a listOf package with default [] (an empty list is a no-op). agents is deliberately an umbrella namespace with no shared programs.agents.enable — each sub-feature self-gates so the namespace stays a clean, mergeable surface another repo could extend. Selection is by derivation: consumers pass skill derivations pulled from packages.<system>, not name strings.

For each selected skill the module writes an individual home.file at ${claude-code.configDir}/skills/<name> with source = <skill derivation> and recursive = true, gated on programs.claude-code.enable, reading configDir from the claude-code module. recursive = true is a hard requirement: it materializes .../skills/<name>/ as a real directory of per-file symlinks so this module, the operator's own skills declarations, and self-placing tool modules coexist under one skills/ tree. The module deliberately does not feed the single-valued programs.claude-code.skills option, which would collide with an operator already setting it.

Verified by the home-manager-module composition check under nix flake check: it instantiates homeModules.default under a sample home-manager configuration selecting a couple of skills with programs.claude-code enabled, builds the resulting home-files derivation, and asserts the per-skill home.file, recursive = true, the claude-code.enable gate, and configDir sourcing all compose as intended (direct analogue of gitea-axi's home-manager-module check).

Acceptance criteria

  • homeModules.default is exposed.
  • It defines programs.agents.skills as listOf package with default [], and an empty list installs nothing.
  • There is no shared programs.agents.enable; the skills feature self-gates.
  • Selection is by derivation (skills pulled from packages.<system>), not by name string.
  • Each selected skill is written as an individual home.file at ${claude-code.configDir}/skills/<name> with recursive = true, sourcing configDir from the claude-code module.
  • Placement is gated on programs.claude-code.enable; with it off, no skill files are written.
  • The module does not set programs.claude-code.skills.
  • The module composes with an operator's own skills declarations and self-placing tool modules under one skills/ tree without collision.
  • nix flake check includes a home-manager-module composition check that builds the home-files derivation for a sample selection and asserts the above.

Implementation Notes

Files: home-manager-module.nix (the module, at the repo root, mirroring gitea-axi's placement), checks/home-manager-module.nix (the composition check), and flake.nix wiring (a homeModules output plus the check registration and the new home-manager input).

  • Placement name comes from skillName. The module reads each derivation's passthru.skillName — the eval-time name attribute task 0001 established for exactly this purpose — to form .../skills/<name> without import-from-derivation.

  • homeModules exposes an agents-skills alias beside default. The criteria only require homeModules.default; the named alias is an additive convenience mirroring gitea-axi's homeModules shape, and default points at it.

  • home-manager flake input follows this flake's nixpkgs. It exists solely so nix flake check can evaluate the module against real home-manager; a consumer importing the module supplies their own home-manager and pkgs, so the input has no bearing on what they get.

  • Recursive placement is proven by the entry's type, not by file existence. A test -f skills/<name>/SKILL.md follows symlinks and cannot distinguish a recursive per-file tree from one opaque symlink over the whole skill, so the check asserts the entry is a real directory (test -d and ! -L). A mutation to recursive = false fails the check.

  • A non-default configDir scenario was added during review. The other scenarios all run at the default configDir, so a module hardcoding .claude/skills/ would have passed them identically. One configuration now sets a custom configDir and asserts placement follows it, closing the "configDir sourcing" leg of the check. A mutation hardcoding .claude fails the check.