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:
48
.claude/tasks/0002-global-placement-home-manager.md
Normal file
48
.claude/tasks/0002-global-placement-home-manager.md
Normal file
@@ -0,0 +1,48 @@
|
||||
---
|
||||
spec: nix-skill-packaging
|
||||
blocked-by: 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
|
||||
|
||||
- [x] `homeModules.default` is exposed.
|
||||
- [x] It defines `programs.agents.skills` as `listOf package` with default `[]`, and an empty list installs nothing.
|
||||
- [x] There is no shared `programs.agents.enable`; the skills feature self-gates.
|
||||
- [x] Selection is by derivation (skills pulled from `packages.<system>`), not by name string.
|
||||
- [x] 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.
|
||||
- [x] Placement is gated on `programs.claude-code.enable`; with it off, no skill files are written.
|
||||
- [x] The module does not set `programs.claude-code.skills`.
|
||||
- [x] The module composes with an operator's own skills declarations and self-placing tool modules under one `skills/` tree without collision.
|
||||
- [x] `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.
|
||||
Reference in New Issue
Block a user