From 029c4a77065f32db35eb3d19c505fb4db729ce62 Mon Sep 17 00:00:00 2001 From: Chris Denneen Date: Mon, 23 Mar 2026 12:53:15 -0400 Subject: [PATCH] Add reusable flake modules and CI eval coverage Signed-off-by: Chris Denneen --- .github/workflows/nix-flake.yaml | 164 ++++++++++++++ README.md | 70 ++++++ flake.lock | 27 +++ flake.nix | 368 +++++++++++++++++++++++++++++++ 4 files changed, 629 insertions(+) create mode 100644 .github/workflows/nix-flake.yaml create mode 100644 flake.lock create mode 100644 flake.nix diff --git a/.github/workflows/nix-flake.yaml b/.github/workflows/nix-flake.yaml new file mode 100644 index 0000000..76ac8f9 --- /dev/null +++ b/.github/workflows/nix-flake.yaml @@ -0,0 +1,164 @@ +name: nix-flake + +on: + pull_request: + paths: + - "flake.nix" + - "flake.lock" + - "README.md" + - "skills/**" + - "agents/**" + - ".github/workflows/nix-flake.yaml" + push: + branches: + - main + paths: + - "flake.nix" + - "flake.lock" + - "README.md" + - "skills/**" + - "agents/**" + - ".github/workflows/nix-flake.yaml" + +jobs: + validate: + runs-on: ubuntu-latest + steps: + - name: Checkout + uses: actions/checkout@v6 + + - name: Install Nix + uses: DeterminateSystems/nix-installer-action@v21 + + + - name: Evaluate flake outputs + run: | + set -euo pipefail + + nix flake show --all-systems + nix eval .#lib.skillNames + nix eval .#lib.agentProfileNames + nix eval .#lib.supportedTools + + test "$(nix eval --raw --apply 'x: if builtins.isFunction x then "true" else "false"' .#homeManagerModules.default)" = "true" + test "$(nix eval --raw --apply 'x: if builtins.isFunction x then "true" else "false"' .#nixosModules.default)" = "true" + test "$(nix eval --raw --apply 'x: if builtins.isFunction x then "true" else "false"' .#darwinModules.default)" = "true" + + - name: Smoke test installer + run: | + set -euo pipefail + + expected="$(find skills -mindepth 1 -maxdepth 1 -type d | wc -l | tr -d ' ')" + tmp="$(mktemp -d)" + + nix run .#install -- --target "$tmp/.agents/skills" + actual_agents="$(find "$tmp/.agents/skills" -mindepth 1 -maxdepth 1 \( -type l -o -type d \) | wc -l | tr -d ' ')" + + nix run .#install -- --tool codex --target "$tmp/.codex/skills" + actual_codex="$(find "$tmp/.codex/skills" -mindepth 1 -maxdepth 1 \( -type l -o -type d \) | wc -l | tr -d ' ')" + + test "$actual_agents" = "$expected" + test "$actual_codex" = "$expected" + + test -f "$tmp/.agents/skills/gitops-knowledge/SKILL.md" + test -f "$tmp/.agents/skills/gitops-repo-audit/SKILL.md" + test -f "$tmp/.agents/skills/gitops-cluster-debug/SKILL.md" + + - name: Consumer flake evals (HM + NixOS + nix-darwin) + run: | + set -euo pipefail + + repo_path="$PWD" + tmp_root="$(mktemp -d)" + + hm_dir="$tmp_root/hm" + mkdir -p "$hm_dir" + cat > "$hm_dir/flake.nix" < "$nixos_dir/flake.nix" < "$darwin_dir/flake.nix" <] [--target ] [--copy] [--force] + + Options: + --tool Use a standard target path for a tool. + Supported: codex, claude-code, gemini, kiro + --target Explicit target directory (overrides --tool mapping) + --copy Copy skill directories instead of symlinking + --force Replace non-symlink existing skill directories + --list Print discovered skills and exit + --help Show this help + + Defaults: + target: .agents/skills + mode: symlink + HELP + } + + mode="symlink" + force=0 + tool="" + target="" + + skills_root='${skills}' + + while [[ $# -gt 0 ]]; do + case "$1" in + --tool) + tool="''${2:-}" + shift 2 + ;; + --target) + target="''${2:-}" + shift 2 + ;; + --copy) + mode="copy" + shift + ;; + --force) + force=1 + shift + ;; + --list) + find "$skills_root" -mindepth 1 -maxdepth 1 -type d -exec basename {} \; | sort + exit 0 + ;; + --help|-h) + usage + exit 0 + ;; + *) + echo "Unknown option: $1" >&2 + usage + exit 2 + ;; + esac + done + + if [[ -z "$target" ]]; then + case "$tool" in + "") + target=".agents/skills" + ;; + codex) + target=".codex/skills" + ;; + claude|claude-code) + target=".claude/skills" + ;; + gemini|gemini-cli|gemeni) + target=".gemini/skills" + ;; + kiro|kiro-cli) + target=".kiro/skills" + ;; + *) + echo "Unsupported --tool value: $tool" >&2 + echo "Supported values: codex, claude-code, gemini, kiro" >&2 + exit 2 + ;; + esac + fi + + mkdir -p "$target" + + installed=0 + for skill_src in "$skills_root"/*; do + [[ -d "$skill_src" ]] || continue + + name="$(basename "$skill_src")" + dst="$target/$name" + + if [[ -e "$dst" || -L "$dst" ]]; then + if [[ -L "$dst" ]]; then + rm -f "$dst" + elif [[ "$force" -eq 1 ]]; then + rm -rf "$dst" + else + echo "Skipping $name: $dst exists (use --force to replace)" >&2 + continue + fi + fi + + if [[ "$mode" == "copy" ]]; then + cp -a "$skill_src" "$dst" + else + ln -s "$skill_src" "$dst" + fi + + installed=$((installed + 1)) + done + + echo "Installed $installed skill(s) to $target" + ''; + }; + in + { + default = skills; + inherit + install + skills + ; + agent-profiles = agentProfiles; + } + ); + + apps = forAllSystems (system: pkgs: { + default = { + type = "app"; + program = "${pkgs.lib.getExe self.packages.${system}.install}"; + }; + + install = { + type = "app"; + program = "${pkgs.lib.getExe self.packages.${system}.install}"; + }; + }); + }; +}