# Builds the home-files tree `homeModules.default` produces under several # operator configurations and asserts on the skills each one places. { pkgs, home-manager, module, mkSkill, }: let inherit (pkgs) lib; # A sample skill built through the flake's own `mkSkill`, so it carries the # `skillName` passthru the module reads to place it. mkSampleSkill = name: mkSkill { inherit pkgs name; src = pkgs.runCommandLocal "${name}-src" { } '' mkdir -p "$out" printf '%s\n' "sample skill ${name}" > "$out/SKILL.md" ''; }; skillAlpha = mkSampleSkill "sample-alpha"; skillBeta = mkSampleSkill "sample-beta"; # A skill the operator declares as their own attribute-set entry, so the # attribute-set assertion can look for it beside the module's. operatorAttrSkill = pkgs.runCommandLocal "operator-attr-skill" { } '' mkdir -p "$out" printf '%s\n' "the operator's own attribute-set skill" > "$out/SKILL.md" ''; # A whole directory of skills, one folder per skill, for the path form of the # operator's own `programs.claude-code.skills`. operatorSkillsDir = pkgs.runCommandLocal "operator-skills-dir" { } '' mkdir -p "$out/operator-dir-skill" printf '%s\n' "the operator's own whole-directory skill" \ > "$out/operator-dir-skill/SKILL.md" ''; # Evaluate the real module through home-manager's standalone entry point and # return the home-files tree it would link into $HOME. homeFiles = operatorConfig: (home-manager.lib.homeManagerConfiguration { inherit pkgs; modules = [ module { home.username = "operator"; home.homeDirectory = "/home/operator"; home.stateVersion = "24.11"; } operatorConfig ]; }).config.home-files; # Two skills selected beside the operator's own attribute-set skill, with # Claude Code enabled. attrSetSkills = homeFiles { programs.claude-code.enable = true; programs.agents.skills = [ skillAlpha skillBeta ]; # A store path, not a bare derivation, so the value takes the option's path # branch rather than its attribute-set branch. programs.claude-code.skills.operator-attr-skill = "${operatorAttrSkill}"; }; # The operator's own skills as a whole directory (path form). # Setting this single-valued option to a path also proves the module never # contributes to it, since an attribute-set contribution would clash with the # path at eval. wholeDirSkills = homeFiles { programs.claude-code.enable = true; programs.agents.skills = [ skillAlpha ]; programs.claude-code.skills = "${operatorSkillsDir}"; }; # A non-default `configDir`, so the assertions can prove placement follows it # rather than a hardcoded `.claude`. customConfigDir = homeFiles { programs.claude-code.enable = true; programs.claude-code.configDir = "/home/operator/.config/claude"; programs.agents.skills = [ skillAlpha ]; }; # Claude Code disabled, with a skill still selected. claudeCodeOff = homeFiles { programs.claude-code.enable = false; programs.agents.skills = [ skillAlpha ]; }; # An empty selection with Claude Code enabled. emptySelection = homeFiles { programs.claude-code.enable = true; programs.agents.skills = [ ]; }; in pkgs.runCommandLocal "skills-home-manager-module-check" { # Forcing each derivation as a build input builds its home-files tree, so an # eval-level composition failure surfaces before any assertion runs. inherit attrSetSkills wholeDirSkills customConfigDir claudeCodeOff emptySelection ; } '' fail() { echo "FAIL: $1" >&2; exit 1; } echo "attribute-set skills: both selected skills and the operator's own land" test -f "$attrSetSkills/.claude/skills/sample-alpha/SKILL.md" \ || fail "selected skill sample-alpha did not land at its own name" test -f "$attrSetSkills/.claude/skills/sample-beta/SKILL.md" \ || fail "selected skill sample-beta did not land at its own name" test -f "$attrSetSkills/.claude/skills/operator-attr-skill/SKILL.md" \ || fail "the operator's own attribute-set skill was clobbered" echo "recursive placement: a skill is a real directory of per-file symlinks" # A file-existence test follows symlinks, so it cannot tell a recursive tree # from one opaque symlink over the whole skill. # The entry's own type is what the assertion reads instead. test -d "$attrSetSkills/.claude/skills/sample-alpha" \ && test ! -L "$attrSetSkills/.claude/skills/sample-alpha" \ || fail "sample-alpha was placed as an opaque symlink, not a recursive tree" echo "whole-directory skills: the selected skill lands beside the operator's directory" test -f "$wholeDirSkills/.claude/skills/sample-alpha/SKILL.md" \ || fail "selected skill did not coexist with a whole skills directory" test -f "$wholeDirSkills/.claude/skills/operator-dir-skill/SKILL.md" \ || fail "the operator's whole skills directory was clobbered" echo "custom configDir: placement follows configDir rather than a hardcoded .claude" test -f "$customConfigDir/.config/claude/skills/sample-alpha/SKILL.md" \ || fail "skill was not placed under the configured configDir" test ! -e "$customConfigDir/.claude/skills/sample-alpha" \ || fail "skill was placed under a hardcoded .claude, ignoring configDir" echo "Claude Code disabled: no skill entry is written despite a selection" test ! -e "$claudeCodeOff/.claude/skills/sample-alpha" \ || fail "a skill was placed with claude-code disabled" echo "empty selection: nothing is installed" test ! -e "$emptySelection/.claude/skills/sample-alpha" \ || fail "an unselected skill was placed" touch "$out" ''