feat(nix): expose declarative outputs and a home-manager module (task 0045) #54

Merged
alexion merged 1 commits from task-0045-declarative-nix-outputs-and-hm-module into main 2026-07-20 13:57:25 -04:00
Owner

Implements .claude/tasks/0045-declarative-nix-outputs-and-hm-module.md.

What was built

Two layers, the second built on the first.

The package installs the bundled Agent Skill to share/gitea-axi/skills/gitea-axi and publishes it as passthru.skill, alongside passthru.sessionStartHook. The hook entry is read from session-start-hook.json, a committed declaration the fast tier reads too — so a new test drives setup hooks against a temporary home and asserts what it writes deep-equals what the file declares, failing if either side drifts (including if the agent SDK changes its envelope).

The flake exposes homeModules.gitea-axi, a wiring layer declaring both from those attributes. It goes through home-manager's own programs.claude-code options rather than writing settings.json directly, so an operator's existing skills and SessionStart hooks compose instead of colliding.

Verified against real home-manager on five configurations: importing without enabling yields a byte-identical generation to never importing at all; enabling alongside a config that already declares its own hook and skill yields both of each; package = null installs no binary but still declares the Skill; the skill-only toggle declares no hook; and omitting programs.claude-code.enable fails the assertion with the intended message.

nix build is green and bit-reproducible under --rebuild; the full fast tier passes (434 tests).

Deviations

  • session-start-hook.json holds the settings entry verbatim, not the fields it is assembled from — declaring the fields would have left the grouping restated in both the Nix expression and the test.
  • package = null still sources the Skill from the default build. The task settled where the binary comes from, not where the Skill's bytes do. Documented on the option rather than designed away.
  • Two limitations documented rather than fixed: home-manager realises the package during evaluation (rebuild latency, not correctness), and programs.claude-code.skills' bare-path form cannot have an entry merged into it.
  • Follow-up: the repo has no README.md, so INSTALL.md — following the existing convention of topic-scoped root documents alongside PUBLISHING.md — is discoverable only by browsing the repo, and does not ship in the npm tarball. Adding a README is its own piece of work.

A ## Gotchas entry was added to CLAUDE.md for a nix flake check failure mode hit during this work: on a dirty tree it intermittently reports the filtered source path as invalid, which reads as an allowlist bug but is an eval-cache artefact.

Review

Risk

Overall: MEDIUM

  • Blast radius: Medium — adds a new flake output and refactors package.nix to finalAttrs form with a new passthru surface, though existing behaviour is preserved.
  • Reversibility: Medium — homeModules.gitea-axi and the passthru attributes are a published Nix interface downstream configurations can pin, so removing them later is breaking rather than a revert.
  • Test coverage: Low — the new fast-tier test closes the one drift seam; the module itself is deliberately unevaluated by nix flake check (ADR 0020).
  • Sensitive domain: Low — no auth, permissions, or data migration; credential handling untouched.
  • Size & complexity: Low — ~200 net lines, mostly prose and declarative option definitions.
  • Runtime criticality: Low — packaging and installation wiring, not the CLI's request path; a broken module fails loudly at evaluation.

Unaddressed findings

Spec — INSTALL.md is not in the npm files allowlist, so it does not ship in the tarball. Left as a flagged follow-up: the real discoverability gap is the absent README.md, and installation instructions inside an already-installed tarball are of little use.

Risk — the module is exercised by no automated test. Accepted deliberately and recorded in ADR 0020: covering it means taking home-manager as a flake input purely to test against, and this matches the spec's existing position that the flake's consumption from a system configuration is verified by the maintainer's rebuild.

Standards findings (test duplication, an inaccurate default in the options table) were fixed and are in the diff.

Implements [`.claude/tasks/0045-declarative-nix-outputs-and-hm-module.md`](.claude/tasks/0045-declarative-nix-outputs-and-hm-module.md). ## What was built Two layers, the second built on the first. **The package** installs the bundled Agent Skill to `share/gitea-axi/skills/gitea-axi` and publishes it as `passthru.skill`, alongside `passthru.sessionStartHook`. The hook entry is read from `session-start-hook.json`, a committed declaration the fast tier reads too — so a new test drives `setup hooks` against a temporary home and asserts what it writes deep-equals what the file declares, failing if either side drifts (including if the agent SDK changes its envelope). **The flake** exposes `homeModules.gitea-axi`, a wiring layer declaring both from those attributes. It goes through home-manager's own `programs.claude-code` options rather than writing `settings.json` directly, so an operator's existing skills and SessionStart hooks compose instead of colliding. Verified against real home-manager on five configurations: importing without enabling yields a **byte-identical** generation to never importing at all; enabling alongside a config that already declares its own hook and skill yields both of each; `package = null` installs no binary but still declares the Skill; the skill-only toggle declares no hook; and omitting `programs.claude-code.enable` fails the assertion with the intended message. `nix build` is green and bit-reproducible under `--rebuild`; the full fast tier passes (434 tests). ## Deviations - **`session-start-hook.json` holds the settings entry verbatim**, not the fields it is assembled from — declaring the fields would have left the grouping restated in both the Nix expression and the test. - **`package = null` still sources the Skill from the default build.** The task settled where the binary comes from, not where the Skill's bytes do. Documented on the option rather than designed away. - **Two limitations documented rather than fixed**: home-manager realises the package during evaluation (rebuild latency, not correctness), and `programs.claude-code.skills`' bare-path form cannot have an entry merged into it. - **Follow-up**: the repo has no `README.md`, so `INSTALL.md` — following the existing convention of topic-scoped root documents alongside `PUBLISHING.md` — is discoverable only by browsing the repo, and does not ship in the npm tarball. Adding a README is its own piece of work. A `## Gotchas` entry was added to `CLAUDE.md` for a `nix flake check` failure mode hit during this work: on a dirty tree it intermittently reports the filtered source path as invalid, which reads as an allowlist bug but is an eval-cache artefact. ## Review ### Risk **Overall: MEDIUM** - Blast radius: Medium — adds a new flake output and refactors `package.nix` to `finalAttrs` form with a new `passthru` surface, though existing behaviour is preserved. - Reversibility: Medium — `homeModules.gitea-axi` and the `passthru` attributes are a published Nix interface downstream configurations can pin, so removing them later is breaking rather than a revert. - Test coverage: Low — the new fast-tier test closes the one drift seam; the module itself is deliberately unevaluated by `nix flake check` (ADR 0020). - Sensitive domain: Low — no auth, permissions, or data migration; credential handling untouched. - Size & complexity: Low — ~200 net lines, mostly prose and declarative option definitions. - Runtime criticality: Low — packaging and installation wiring, not the CLI's request path; a broken module fails loudly at evaluation. ### Unaddressed findings **Spec — `INSTALL.md` is not in the npm `files` allowlist, so it does not ship in the tarball.** Left as a flagged follow-up: the real discoverability gap is the absent `README.md`, and installation instructions inside an already-installed tarball are of little use. **Risk — the module is exercised by no automated test.** Accepted deliberately and recorded in ADR 0020: covering it means taking home-manager as a flake input purely to test against, and this matches the spec's existing position that the flake's consumption from a system configuration is verified by the maintainer's rebuild. Standards findings (test duplication, an inaccurate default in the options table) were fixed and are in the diff.
alexion added 1 commit 2026-07-20 13:51:52 -04:00
feat(nix): expose declarative outputs and a home-manager module (task 0045)
All checks were successful
CI / test (22) (pull_request) Successful in 49s
CI / test (true, 24) (pull_request) Successful in 1m5s
CI / flake (pull_request) Successful in 3s
CI / test (22) (push) Successful in 49s
CI / test (true, 24) (push) Successful in 1m5s
CI / flake (push) Successful in 3s
e77a1f5e22
Let a Nix configuration declare gitea-axi's ambient context instead of
running a command that writes it. `setup` and `setup hooks` are write-only
against files the operator is assumed to own, so an operator whose agent
configuration is generated cannot use them at all.

The package installs the bundled Agent Skill to share/gitea-axi/skills and
publishes it as `passthru.skill`, alongside `passthru.sessionStartHook` read
from `session-start-hook.json` — a committed declaration the fast tier reads
too, so a test drives `setup hooks` and asserts the two agree.

On top of that, `homeModules.gitea-axi` declares both from those attributes
through home-manager's own Claude Code options, so an operator's existing
skills and SessionStart hooks compose rather than collide. Importing it
without enabling it yields a byte-identical generation.

The spec's Out of Scope entry deferring a home-manager module is deleted;
ADR 0020 records the reversal, and INSTALL.md describes both paths.
alexion merged commit e77a1f5e22 into main 2026-07-20 13:57:25 -04:00
alexion deleted branch task-0045-declarative-nix-outputs-and-hm-module 2026-07-20 13:57:25 -04:00
Sign in to join this conversation.
No Reviewers
No Label
1 Participants
Notifications
Due Date
No due date set.
Dependencies

No dependencies set.

Reference: alexion/gitea-axi#54