feat: package skills as per-skill Nix derivations (task 0001) #1
Reference in New Issue
Block a user
Delete Branch "task-0001-content-tier-skill-packaging"
Deleting a branch is permanent. Although the deleted branch may continue to exist for a short time before it actually gets removed, it CANNOT be undone in most cases. Continue?
Task:
.claude/tasks/0001-content-tier-skill-packaging.md(spec:nix-skill-packaging)Summary
Adds the content tier: a standalone Nix flake that packages each skill as an individually addressable derivation.
flake.nix— hand-rolledforAllSystemsover the three systems,nixpkgsunstable, no flake-utils/flake-parts (mirrors the siblinggitea-axiflake).lib/discover.nix— recursive walk ofskills/: any directory with aSKILL.mdis a skill at any depth, its subfolders are then assets; directories without one are descended through as cosmetic containers. Globally-unique names enforced with a hard eval-time collision error.lib/mk-skill.nix—lib.mkSkill, the skill-derivation contract:$outholdsSKILL.mdat its root plus assets, andpassthru.skillNamecarries the name at eval time (no import-from-derivation). Nothing here references.claude/skillsor anything Claude-Code-specific.packages.<system>.<name>; there is nodefault.checks/skill-build.nix(+ fixtures) — thenix flake checkseam: drives fixture skills through the whole tier and asserts a two-skills-one-name collision fixture fails discovery at eval viabuiltins.tryEval.The repo ships no real skill content yet, so
skills/holds only a.gitkeepandpackages.<system>is empty today.Deviations
SKILL.mdfrontmatter. The acceptance criteria only require the leaf directory name; the two are equal by Claude Code convention, and parsing frontmatter at eval would add complexity for no behavioural gain. Documented in the task's Implementation Notes.checks.<system>(beyond the literal criteria) sonix flake checkbuilds each real skill once they land and fails on a malformed one (spec user story 17). Empty today.Review
/review-uncommitted— Risk: Low (worst factor), no correctness findings.Risk
Overall: LOW
skills/tree so nothing downstream builds yet.lib.mkSkillis a new API surface but has no consumers yet.nix flake checkdriving fixtures for every discovery shape plus the collision hard-error.cp -Rinto$outand a directory walk.Unaddressed findings
None. The Standards axis raised one judgement-call (duplicated contract prose across
flake.nixandchecks/skill-build.nix) which is fixed in the diff. The Spec axis's only note — the unimplemented frontmatter-name branch — is within the letter of the acceptance criteria and recorded as a deliberate decision in the Implementation Notes; nothing left unaddressed.@@ -0,0 +2,4 @@## Building this under `nix flake check` exercises the whole content tier at once.# The repo ships no real skill content, so the check drives fixture skills under# `./fixtures`, including a collision fixture that must fail discovery at eval.This is temporary state of the repo and should not be worked around. Keep the fixtures though, but the comments make it sound like this project will never have skills.
@@ -0,0 +1,82 @@{description = "Personal Claude Code agent skills, packaged through Nix.";Remove reference to Claude Code.
@@ -0,0 +3,4 @@# Tracks unstable to match the maintainer's system.# This input governs standalone builds and `nix flake check` only.# A consumer deduplicates by pointing it at their own nixpkgs.Bad comment
@@ -0,0 +18,4 @@"aarch64-darwin"];forAllSystems =Wasn't there a built in flake function for this?
@@ -0,0 +35,4 @@# Turns a skill source directory into a conforming derivation.# Exposed as `lib.mkSkill` so a self-packaging tool repo can reuse the# same contract.These comments should be on the function definition and not here?
@@ -0,0 +45,4 @@# The skills this repo itself ships, empty today.# A skill is added by creating `skills/<...>/<name>/SKILL.md`, and the walk# finds it with no edit to this flake.ownSkills = discoverSkills ./skills;bad variable name?
Reworked the comments substantively (not just the earlier style pass). Amended into the branch — replies inline below.
@@ -0,0 +2,4 @@## Drives fixture skills through the whole content tier under `nix flake check`.# The fixtures pin every discovery shape and the collision hard-error# deterministically, independent of whichever real skills the repo carries.Reworded so the fixtures read as deliberate, permanent coverage — they pin every discovery shape and the collision hard-error deterministically, independent of whichever real skills the repo carries — rather than a workaround for today's empty tree. Fixtures kept as-is.
@@ -0,0 +1,60 @@{description = "Personal agent skills, packaged through Nix.";Done. Dropped "Claude Code" from the flake description (now "Personal agent skills, packaged through Nix.") and removed the remaining Claude Code mentions from the content-tier comments too — the tier is tool-agnostic, so naming the tool even to disclaim it was out of place.
@@ -0,0 +3,4 @@inputs.nixpkgs.url = "github:NixOS/nixpkgs/nixos-unstable";outputs =Removed entirely. The
nixos-unstableURL is self-documenting, and the rest was either obvious (a consumer overriding the input) or described downstream consumers.@@ -0,0 +18,4 @@forAllSystems =f:nixpkgs.lib.genAttrs systems (system:There's no
forAllSystemsinnixpkgs.lib—genAttrs(used inside the helper) is the primitive it's built from. The named wrappers are flake-utils (eachDefaultSystem) and flake-parts (perSystem), both deliberately avoided per the spec to mirror the sibling gitea-axi flake and keep the input set minimal. Happy to pull in flake-utils for the shorter form if you'd rather — just say so and I'll switch both this repo and gitea-axi to stay consistent.@@ -0,0 +35,4 @@nixpkgs.lib.listToAttrs (map (skill: {inherit (skill) name;value = mkSkill { inherit pkgs; inherit (skill) name src; };Agreed — removed it from the import site. The description already lives on the definition in
lib/mk-skill.nix, so themkSkill = import ./lib/mk-skill.nix;line now stands on its own.@@ -0,0 +45,4 @@# No `default`: a catalog has no single default skill.packages = forAllSystems ({ pkgs, ... }: skillPackages pkgs);# Each discovered skill is folded in as its own check, so `nix flake check`Renamed
ownSkills→discoveredSkills.Adopted flake-utils per your call — amended into the branch.
@@ -0,0 +18,4 @@map (skill: {inherit (skill) name;value = mkSkill { inherit pkgs; inherit (skill) name src; };}) discoveredSkillsSwitched to
flake-utils.lib.eachSystem. To be precise about the earlier answer: there is no officialforAllSystems/eachSysteminnixpkgs.lib(verified —genAttrsis the only primitive there), so this pulls innumtide/flake-utilsas an input. It useseachSystemwith an explicit three-system list rather thaneachDefaultSystem, because the default set includesx86_64-darwin, which recent nixpkgs dropped and whoselegacyPackagesnow throws.nix flake checkpasses and the output shape is unchanged (packages.<system>.<name>,checks.<system>, system-independentlib). Updated the spec and task doc, which had recorded the hand-rolled/no-flake-utils decision. Left the sibling gitea-axi flake alone for now — happy to make the same change there separately if you want the two consistent.5b6c158d2eto8622b35f48