@@ 0,0 1,86 @@
+{
+ config,
+ lib,
+ pkgs,
+ agentEnvLib,
+ ...
+}:
+let
+ inherit (lib) mkOption types;
+
+ sortedInstructions = lib.sort (
+ a: b: if a.order == b.order then a.name < b.name else a.order < b.order
+ ) (lib.mapAttrsToList (name: v: v // { inherit name; }) config.instructions);
+
+ # Transitive closure over skillDeps, keyed by store path.
+ skillClosure = map (e: e.skill) (
+ builtins.genericClosure {
+ startSet = map (s: {
+ key = s.outPath;
+ skill = s;
+ }) config.skills.packages;
+ operator =
+ e:
+ map (s: {
+ key = s.outPath;
+ skill = s;
+ }) e.skill.skillDeps;
+ }
+ );
+ skillsByName = lib.groupBy (s: s.skillName) skillClosure;
+in
+{
+ options = {
+ instructions = mkOption {
+ default = { };
+ description = "Fragments of the instruction file (CLAUDE.md / AGENTS.md), sorted by order, then name.";
+ type = types.attrsOf (
+ types.submodule {
+ options = {
+ text = mkOption { type = types.lines; };
+ order = mkOption {
+ type = types.int;
+ default = 1000;
+ };
+ };
+ }
+ );
+ };
+
+ skills.packages = mkOption {
+ type = types.listOf types.package;
+ default = [ ];
+ description = "Skills built with lib.mkSkill. Their skill dependencies are added automatically.";
+ };
+
+ # Read-only results used by the agent and launcher modules.
+ build = mkOption {
+ type = types.attrsOf types.anything;
+ default = { };
+ internal = true;
+ };
+
+ assertions = mkOption {
+ type = types.listOf types.attrs;
+ default = [ ];
+ internal = true;
+ };
+
+ };
+
+ config = {
+ build = {
+ instructionsText =
+ lib.concatMapStringsSep "\n\n" (i: lib.removeSuffix "\n" i.text) sortedInstructions + "\n";
+ skills = lib.mapAttrsToList (_: ss: lib.head ss) skillsByName;
+ skillRuntimeInputs = lib.unique (lib.concatMap (s: s.runtimeInputs) skillClosure);
+ };
+
+ assertions = lib.mapAttrsToList (name: ss: {
+ assertion = lib.length ss == 1;
+ message = "skill name '${name}' is provided by different packages: ${
+ lib.concatMapStringsSep ", " (s: s.outPath) ss
+ }";
+ }) skillsByName;
+ };
+}