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.
This commit was merged in pull request #2.
This commit is contained in:
2026-07-22 17:18:05 -04:00
parent 8622b35f48
commit b0a763e5ce
5 changed files with 314 additions and 1 deletions

63
home-manager-module.nix Normal file
View File

@@ -0,0 +1,63 @@
# Places operator-selected skills into Claude Code's skills directory, one per
# skill, for every project.
{
config,
lib,
...
}:
let
cfg = config.programs.agents;
claudeCode = config.programs.claude-code;
in
{
options.programs.agents.skills = lib.mkOption {
type = lib.types.listOf lib.types.package;
default = [ ];
example = lib.literalExpression "[ inputs.skills.packages.\${system}.some-skill ]";
description = ''
Skills to install globally, into the `skills/` subdirectory of
{option}`programs.claude-code.configDir`, so they are active in every
project.
Selection is by derivation: pass skill derivations from the skills flake's
`packages.<system>`, each of which carries its placement name as an
eval-time attribute. An empty list installs nothing.
Every skill is written as its own recursive {option}`home.file`, so this
option composes with an operator's own {option}`programs.claude-code.skills`
and with other modules placing skills under the same directory rather than
claiming the tree. Placement is gated on
{option}`programs.claude-code.enable`.
'';
};
# `agents` is an umbrella namespace with no shared enable, so the skills
# feature self-gates.
# An empty list writes nothing, and files land only when Claude Code is
# enabled.
# Reading each skill's source realises its derivation, so gating on
# `claude-code.enable` also keeps that realisation off a host that installs
# nothing.
config = lib.mkIf claudeCode.enable {
# `configDir` comes from the Claude Code module, so skills land beside its
# own wherever the operator points it.
# Each name is the derivation's eval-time `skillName`, so placement reads no
# `$out` and needs no import-from-derivation.
# `recursive = true` materialises `skills/<name>/` as a directory of per-file
# symlinks, which is what lets this module, the operator's own skills, and
# self-placing tool modules share one `skills/` tree.
#
# This deliberately does not feed the single-valued
# `programs.claude-code.skills`, which would collide with an operator already
# setting it.
home.file = lib.listToAttrs (
map (skill: {
name = "${claudeCode.configDir}/skills/${skill.skillName}";
value = {
source = skill;
recursive = true;
};
}) cfg.skills
);
};
}