10 KiB
Repo-Lib Audit
Date: 2026-03-21
Direct Answers
1. Does it work today?
Partially.
nix flake show --all-systemssucceeds from the repo root.- Before this audit,
nix flake checkfailed because the old shell-based release test harness relied on a brittle regex overnix derivation showinternals instead of asserting against stable JSON structure. mkRepo, the template flake, the dev shell, and the formatter/check outputs all evaluate. The primary failure was test-harness fragility, not a clear functional break in the library itself.- The release path still carries real operational risk because
packages/release/release.shcombines mutation, formatting, commit, tag, and push in one command and uses destructive rollback.
2. Is the code organization maintainable?
Not in its current shape.
packages/repo-lib/lib.nixis a single 879-line module that owns unrelated concerns:- tool schema normalization
- hook/check config synthesis
- shell banner generation
- release step normalization
- public API assembly
packages/repo-lib/shell-hook.shis not just presentation. It contains tool probing, parsing, error handling, and shell-failure behavior.mkDevShellandmkRepooverlap conceptually and preserve legacy paths that make the main implementation harder to reason about.
3. Is the public API readable and usable for consumers?
Usable, but underspecified and harder to learn than the README suggests.
README.mdpresentsmkRepoas a compact abstraction, but the real behavior is distributed acrosslib.nix,shell-hook.sh, generated Lefthook config, and the release script.- The boundaries between
config,perSystem,checks,lefthook,shell,tools, andreleaseare not obvious from the docs alone. - Raw
config.lefthook/perSystem.lefthookpassthrough is effectively required for advanced use, which means the higher-levelchecksabstraction is incomplete.
4. Which parts should be kept, split, or replaced?
Keep:
- the high-level
mkRepoconsumer entrypoint, if compatibility matters - the template
- the structured release-step idea
Split:
- release tooling from repo shell/hook wiring
- shell banner rendering from shell/package assembly
- hook/check generation from the rest of
mkRepo
Replace:
- custom flake output composition with
flake-parts - custom hook glue with
lefthook.nix - keep
treefmt-nixas the formatting layer rather than wrapping it deeper
5. What is the lowest-complexity target architecture?
Option A: keep repo-lib.lib.mkRepo as a thin compatibility wrapper, but rebase its internals on established components:
flake-partsfor flake structure andperSystemtreefmt-nixfor formattinglefthook.nixfor hooks- a separate
mkReleasepackage for release automation, with explicit opt-ins for commit/tag/push
That preserves migration cost for consumers while removing most of the custom orchestration burden from this repo.
Correctness Findings
High: self-checks were failing because the test harness depended on unstable derivation internals
Files:
- the removed shell-based release test harness
Details:
- The repo did not pass
nix flake checkon the host system before this audit because the tests aroundlefthook-checkassumednix derivation showwould expose"/nix/store/...-lefthook.yml.drv"as a quoted string. - Current Nix emits input derivations in a different JSON shape, so the regex broke even though the underlying derivation still existed.
- This is a release blocker because the repo’s own baseline was red.
Assessment:
- Fixed in this audit by replacing the ad hoc scrape with a helper that locates the relevant input derivation from the JSON more defensibly.
High: release rollback is destructive
Files:
Details:
revert_on_failurerunsgit reset --hard "$START_HEAD"after any trapped error.- That will discard all working tree changes created during the release flow, including user-visible file changes that might be useful for debugging or manual recovery.
Assessment:
- This is too aggressive for a library-provided command.
- Rollback should be opt-in, staged to a temp branch/worktree, or replaced with a safer failure mode that leaves artifacts visible.
Medium: release performs too many side effects in one irreversible flow
Files:
Details:
- The default flow updates version state, runs release steps, formats, stages, commits, tags, and pushes.
- There is no dry-run mode.
- There is no
--no-push,--no-tag, or--no-commitmode. - The command is framed as a package generated by the library, so consumers inherit a strong opinionated workflow whether they want it or not.
Assessment:
- Release should be separated from repo shell wiring and broken into explicit phases or flags.
Organization And Readability Findings
High: lib.nix is a monolith
Files:
Details:
- One file owns normalization helpers, shell assembly, banner formatting inputs, Lefthook synthesis, release templating, compatibility APIs, and top-level outputs.
- The public API is therefore not separable from its implementation detail.
Assessment:
- This is the main maintainability problem in the repo.
- Even if behavior is mostly correct, the cost of safely changing it is too high.
Medium: shell UX logic is coupled to operational behavior
Files:
Details:
- Tool banners do more than render text. They probe commands, parse versions, print failures, and may exit the shell startup for required tools.
- That behavior is not obvious from the README example and is spread across generated shell script fragments.
Assessment:
- The banner feature is nice, but it is expensive in complexity and debugging surface relative to the value it adds.
- If retained, it should be optional and isolated behind a smaller interface.
Medium: legacy compatibility paths dominate the core implementation
Files:
Details:
mkDevShelluses legacy tool normalization and its own feature toggles.mkRepocarries a newer strict tool shape.- Both flows feed the same shell-artifact builder, which means the common implementation has to keep both mental models alive.
Assessment:
- Deprecate
mkDevShellonce a thinmkRepowrapper exists over standard components.
Public API And Usability Findings
High: README underspecifies the real API
Files:
Details:
- The README explains the happy-path shape of
mkRepo, but not the actual behavioral contract. - It does not provide a reference for:
- tool spec fields
- shell banner behavior
- exact merge order between
configandperSystem - what the
checksabstraction cannot express - what
releaseis allowed to mutate by default
Assessment:
- Consumers can start quickly, but they cannot predict behavior well without reading the implementation.
Medium: abstraction boundaries are blurry
Files:
Details:
checkslooks like the high-level hook API, but advanced usage requires raw Lefthook passthrough.shell.bootstrapis documented as the purity escape hatch, but the template uses it for tool bootstrapping and operational setup.releaseis presented as optional packaging, but it is operational automation with repo mutation and remote side effects.
Assessment:
- These concepts should be separate modules with narrower contracts.
Replacement Options
Option A: thin compatibility layer
Keep repo-lib.lib.mkRepo, but make it a wrapper over standard components.
Use:
flake-partsfor top-level flake assembly andperSystemtreefmt-nixfor formattinglefthook.nixfor Git hooks- a standalone
mkReleaseoutput for release automation
Pros:
- lower migration cost
- preserves existing entrypoint
- reduces bespoke glue
Cons:
- some compatibility debt remains
- requires a staged migration plan
Option B: full replacement
Stop positioning this as a general-purpose Nix library and keep only:
- the template
- any repo-specific release helper
- migration docs to standard tools
Pros:
- lowest long-term maintenance burden
- clearest product boundary
Cons:
- highest consumer migration cost
- discards the existing
mkRepoAPI
Final Recommendation
Choose Option A.
Rationale:
mkRepohas enough consumer value to keep as a compatibility surface.- Most of the complexity is not unique value. It is custom orchestration around capabilities already provided by better-maintained ecosystem tools.
- The release flow should be split out regardless of which option is chosen.
Concrete target:
- Rebase flake structure on
flake-parts. - Replace custom hook synthesis with
lefthook.nix. - Keep
treefmt-nixdirectly exposed instead of deeply wrapped. - Make shell banners optional or move them behind a very small isolated module.
- Move release automation into a separate package with explicit side-effect flags.
- Mark
mkDevShelldeprecated oncemkRepois stable on the new internals.
Migration Cost And Compatibility Notes
- A thin compatibility wrapper keeps consumer migration reasonable.
- The biggest compatibility risk is release behavior, because some consumers may depend on the current commit/tag/push flow.
- Introduce safer release behavior behind new flags first, then deprecate the old all-in-one default.
- Keep template output working during the transition; it is currently the clearest example of intended usage.
Required Validation For Follow-Up Work
nix flake show --all-systemsnix flake check- minimal consumer repo using
mkRepo - template repo evaluation
- release smoke test in a temporary git repo
- hook assertions that do not depend on private derivation naming/layout
Sources
flake-parts: https://flake.parts/treefmt-nix: https://github.com/numtide/treefmt-nixlefthook.nix: https://github.com/cachix/lefthook.nixdevenv: https://github.com/cachix/devenv