# Repo-Lib Audit Date: 2026-03-21 ## Direct Answers ### 1. Does it work today? Partially. - `nix flake show --all-systems` succeeds from the repo root. - Before this audit, `nix flake check` failed because the old shell-based release test harness relied on a brittle regex over `nix derivation show` internals 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.sh`](../../../packages/release/release.sh) combines 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.nix`](../../../packages/repo-lib/lib.nix) is 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.sh`](../../../packages/repo-lib/shell-hook.sh) is not just presentation. It contains tool probing, parsing, error handling, and shell-failure behavior. - `mkDevShell` and `mkRepo` overlap 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.md`](../../../README.md) presents `mkRepo` as a compact abstraction, but the real behavior is distributed across `lib.nix`, `shell-hook.sh`, generated Lefthook config, and the release script. - The boundaries between `config`, `perSystem`, `checks`, `lefthook`, `shell`, `tools`, and `release` are not obvious from the docs alone. - Raw `config.lefthook` / `perSystem.lefthook` passthrough is effectively required for advanced use, which means the higher-level `checks` abstraction is incomplete. ### 4. Which parts should be kept, split, or replaced? Keep: - the high-level `mkRepo` consumer 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-nix` as 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-parts` for flake structure and `perSystem` - `treefmt-nix` for formatting - `lefthook.nix` for hooks - a separate `mkRelease` package 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 check` on the host system before this audit because the tests around `lefthook-check` assumed `nix derivation show` would 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: - [`packages/release/release.sh`](../../../packages/release/release.sh) Details: - `revert_on_failure` runs `git 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: - [`packages/release/release.sh`](../../../packages/release/release.sh) 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-commit` mode. - 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: - [`packages/repo-lib/lib.nix`](../../../packages/repo-lib/lib.nix) 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: - [`packages/repo-lib/shell-hook.sh`](../../../packages/repo-lib/shell-hook.sh) - [`packages/repo-lib/lib.nix`](../../../packages/repo-lib/lib.nix) 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: - [`packages/repo-lib/lib.nix`](../../../packages/repo-lib/lib.nix) Details: - `mkDevShell` uses legacy tool normalization and its own feature toggles. - `mkRepo` carries 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 `mkDevShell` once a thin `mkRepo` wrapper exists over standard components. ## Public API And Usability Findings ### High: README underspecifies the real API Files: - [`README.md`](../../../README.md) 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 `config` and `perSystem` - what the `checks` abstraction cannot express - what `release` is 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: - [`README.md`](../../../README.md) - [`template/flake.nix`](../../../template/flake.nix) - [`packages/repo-lib/lib.nix`](../../../packages/repo-lib/lib.nix) Details: - `checks` looks like the high-level hook API, but advanced usage requires raw Lefthook passthrough. - `shell.bootstrap` is documented as the purity escape hatch, but the template uses it for tool bootstrapping and operational setup. - `release` is 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-parts` for top-level flake assembly and `perSystem` - `treefmt-nix` for formatting - `lefthook.nix` for Git hooks - a standalone `mkRelease` output 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 `mkRepo` API ## Final Recommendation Choose **Option A**. Rationale: - `mkRepo` has 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: 1. Rebase flake structure on `flake-parts`. 2. Replace custom hook synthesis with `lefthook.nix`. 3. Keep `treefmt-nix` directly exposed instead of deeply wrapped. 4. Make shell banners optional or move them behind a very small isolated module. 5. Move release automation into a separate package with explicit side-effect flags. 6. Mark `mkDevShell` deprecated once `mkRepo` is 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-systems` - `nix 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-nix - `lefthook.nix`: https://github.com/cachix/lefthook.nix - `devenv`: https://github.com/cachix/devenv