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.
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.defaultis exposed.- It defines
programs.agents.skillsaslistOf packagewith 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.fileat${claude-code.configDir}/skills/<name>withrecursive = true, sourcingconfigDirfrom 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 checkincludes 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'spassthru.skillName— the eval-time name attribute task 0001 established for exactly this purpose — to form.../skills/<name>without import-from-derivation. -
homeModulesexposes anagents-skillsalias besidedefault. The criteria only requirehomeModules.default; the named alias is an additive convenience mirroring gitea-axi'shomeModulesshape, anddefaultpoints at it. -
home-managerflake input follows this flake's nixpkgs. It exists solely sonix flake checkcan 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.mdfollows 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 -dand! -L). A mutation torecursive = falsefails the check. -
A non-default
configDirscenario was added during review. The other scenarios all run at the defaultconfigDir, so a module hardcoding.claude/skills/would have passed them identically. One configuration now sets a customconfigDirand asserts placement follows it, closing the "configDir sourcing" leg of the check. A mutation hardcoding.claudefails the check.