Files
skills/lib/discover.nix
alexion 8622b35f48 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.
2026-07-22 16:51:40 -04:00

43 lines
1.3 KiB
Nix

# 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