# `checks.module-eval-agent-plugins` — see ./lib.nix for the shared # rationale (why this suite exists, naming convention, "evaluates # not executes"). { pkgs, lib, self, nixosSystem, }: let inherit (import ./lib.nix { inherit pkgs lib self nixosSystem ; }) agent runGroup ; # `claudePlugins`'s additive-merge shape: a plain per-agent # definition must ADD to the module's own base-set definition rather than # replacing it, while `lib.mkForce` must still replace the whole list # outright — the two arms below plus the unset default (read directly off # `bare`-shaped `agent { }`, no fixture of its own needed) are the three # cases that shape has to hold. agentPluginsDefault = agent { }; agentPluginsAdded = agent { claudePlugins = [ "foo@bar" ]; }; agentPluginsForced = agent { claudePlugins = lib.mkForce [ "foo@bar" ]; }; # The de-dup arm: an agent that names a base-set entry explicitly must not # get it installed twice. agentPluginsDuplicate = agent { claudePlugins = [ "base@hyperhive" ]; }; agentPlugins = machine: machine.services.hyperhive.agent.claudePlugins; cases = [ { # Case 1 of the additive-merge shape: an agent that declares # nothing gets exactly the module's base set, no more and no less. name = "an agent with no claudePlugins definition gets exactly the base set"; ok = agentPlugins agentPluginsDefault == [ "skill-creator@claude-plugins-official" "base@hyperhive" ]; } { # Case 2: a plain per-agent definition ADDS to the base set (list-typed # options at equal priority concatenate) rather than replacing it — # the property the operator ruling asked for instead of `mkDefault`. # Sorted before comparing: concatenation order between two same- # priority definitions is a module-system implementation detail this # case isn't about — membership and count are. name = "an agent's own claudePlugins definition adds to the base set"; ok = builtins.sort builtins.lessThan (agentPlugins agentPluginsAdded) == [ "base@hyperhive" "foo@bar" "skill-creator@claude-plugins-official" ]; } { # Case 3: `lib.mkForce` is still the escape hatch — an operator who # wants the base set gone, not extended, can still say so outright. name = "an agent's mkForce claudePlugins replaces the base set outright"; ok = agentPlugins agentPluginsForced == [ "foo@bar" ]; } { # De-dup arm: an agent that names a base-set entry itself must not get # it installed twice — `lib.unique` at the JSON-render site, not the # option's merge (the merge is a plain concatenation on purpose, so # the un-deduped list stays readable for `agentPlugins` above). name = "an agent repeating a base-set plugin does not get it installed twice"; ok = builtins.sort builtins.lessThan ( builtins.fromJSON agentPluginsDuplicate.environment.etc."hyperhive/claude-plugins.json".text ) == [ "base@hyperhive" "skill-creator@claude-plugins-official" ]; } ]; in runGroup "agent-plugins" cases