feat: package skills as per-skill Nix derivations (task 0001)
Add the content tier: a standalone flake that auto-discovers each skill (a directory containing a SKILL.md, at any depth) and exposes it as an individually addressable derivation built by lib.mkSkill. Directories without a SKILL.md are descended through as cosmetic containers; once a SKILL.md is found, that directory's subfolders are its assets, not further skills. Skill names must be globally unique across the tree — a collision is a hard eval-time error, not a warning. A fixture-driven skill-build check under `nix flake check` exercises the recursive walk, the builder, the SKILL.md-at-$out-root contract, the eval-time name passthru, and the collision error. The repo ships no real skill content yet, so packages.<system> is empty today.
This commit is contained in:
42
lib/discover.nix
Normal file
42
lib/discover.nix
Normal file
@@ -0,0 +1,42 @@
|
||||
# Recursive skill discovery: one `{ name; src; }` per skill in a source tree.
|
||||
lib: root:
|
||||
let
|
||||
walk =
|
||||
dir:
|
||||
let
|
||||
subdirs = lib.filterAttrs (_: type: type == "directory") (builtins.readDir dir);
|
||||
in
|
||||
lib.concatLists (
|
||||
lib.mapAttrsToList (
|
||||
entryName: _:
|
||||
let
|
||||
child = dir + "/${entryName}";
|
||||
in
|
||||
# A directory with a SKILL.md is a skill, and its subdirectories are
|
||||
# that skill's assets rather than nested skills, so the walk stops here.
|
||||
# A directory without one is a cosmetic container to recurse through.
|
||||
if (builtins.readDir child) ? "SKILL.md" then
|
||||
[
|
||||
{
|
||||
name = entryName;
|
||||
src = child;
|
||||
}
|
||||
]
|
||||
else
|
||||
walk child
|
||||
) subdirs
|
||||
);
|
||||
|
||||
found = walk root;
|
||||
names = map (s: s.name) found;
|
||||
duplicates = lib.unique (lib.filter (n: lib.count (x: x == n) names > 1) names);
|
||||
in
|
||||
if duplicates != [ ] then
|
||||
throw ''
|
||||
Skill name collision: ${lib.concatStringsSep ", " duplicates}.
|
||||
Two or more skills under ${toString root} resolve to the same name.
|
||||
A skill's name is its leaf directory name and must be globally unique
|
||||
across the whole tree, independent of the cosmetic folders above it.
|
||||
Rename one of the colliding skills.''
|
||||
else
|
||||
found
|
||||
Reference in New Issue
Block a user