Rust + Bun/TypeScript workspace template with Nix (devenv), moon tasks, treefmt, cargo-deny, prek git hooks, and optional Cachix.
This repo uses a git submodule at .cursor/agency-agents. Clone with submodules in one go:
git clone --recurse-submodules <repo-url>
cd <repo>If you already cloned without submodules, init and update them:
git submodule update --init --recursive- Runtimes: Rust (stable), JavaScript/TypeScript (Bun).
- Environment: Nix via devenv; reproducible toolchain and scripts.
- Tasks: moon for format, check, lint, build, test, docs, audit, coverage, etc.
- Git hooks: prek (pre-commit–compatible): pre-push runs the full moon pipeline; commit-msg enforces conventional commits (commitizen).
- Formatting: treefmt over Rust, Nix, shell, JS/TS, YAML, TOML.
- Rust quality: cargo-deny (advisories/licenses), cargo-audit, clippy, nextest, optional llvm-cov.
| Tool | Purpose | Config |
|---|---|---|
| devenv | Nix dev shell: languages (Rust, Bun, TS), packages, env vars, scripts. Enter with devenv shell. |
devenv.nix, devenv.yaml |
| moon | Task runner: format, check, lint, build, test, docs, fix, bench, audit, coverage, ci-format. | moon.yml |
| prek | Git hooks (no Python). Installs from .pre-commit-config.yaml on devenv shell. |
.pre-commit-config.yaml |
| treefmt | Single CLI to format all supported files (only tracked files by default). | treefmt.toml, treefmt.ci.toml |
| rustfmt | Rust formatter (invoked by treefmt). | rustfmt.toml |
| cargo-deny | Rust: advisories, licenses, duplicate deps. Run manually or in CI. | deny.toml |
| cargo-audit | Rust security advisories. Used in moon :audit and pre-push. |
— |
| cargo-nextest | Fast Rust test runner. Used in moon :test and :coverage. |
nextest.toml |
| sccache | Shared compilation cache for Rust (and C/C++) across builds. | env RUSTC_WRAPPER=sccache |
| mold | Fast linker for Rust on Linux. | — |
| commitizen | Validates commit messages (conventional commits). Prek hook on commit-msg. | — |
| Cachix | Optional Nix binary cache; pull/push configured in devenv.nix. |
cachix.pull / cachix.push |
- Nix: deadnix, alejandra
- GitHub Actions: actionlint
- Bash: beautysh
- JS/TS/JSON: biome
- YAML: yamlfmt
- TOML: taplo
- Rust: rustfmt (edition 2024)
Cachix is a Nix binary cache. It stores build results (e.g. devenv shell closure, Nix packages) so you and CI can pull instead of rebuilding. Optionally, CI can push new build results to the cache so the next run is fast.
In devenv.nix the cache is configured as:
cachix = {
pull = ["project-template"];
push = "project-template";
};- pull — When you run
devenv shellor any Nix command, Nix will try to fetch store paths from theproject-templatecache (if you’ve trusted it; see below). No account needed for pull. - push — When a build runs with push enabled and Cachix auth, new store paths can be uploaded to the
project-templatecache. Pushing is typically done only in CI (e.g. on the main branch).
Replace project-template with your own cache name if you create one at cachix.org.
-
Trust the cache so Nix uses it when building the dev shell:
cachix use project-template
(Use your cache name if different.) This adds the cache to your Nix config and lets
devenv shellpull binaries instead of building everything. -
Enter the shell as usual:
devenv shell. If the closure is already on the cache, it will download instead of build.
You do not need a Cachix account or auth for pulling. For pushing (e.g. from CI) you need a Cachix auth token.
In GitHub Actions (or similar) you want to:
- Use the cache on every run (pull) so CI benefits from previous builds.
- Push to the cache only on selected events (e.g. push to
main), so the cache stays up to date and you avoid push failures on PRs.
Example pattern:
-
Install Nix (e.g.
DeterminateSystems/nix-installer-action). -
Configure Cachix with cachix/cachix-action:
name: your cache name (e.g.project-template).- Pull: always enabled when
nameis set. - Push: only when you want to update the cache. Pass the auth token only then and set
skipPush: trueotherwise, so the action doesn’t try to start the push daemon on PRs.
Example (conceptual):
env: CACHIX_AUTH_TOKEN: ${{ secrets.CACHIX_AUTH_TOKEN }} steps: - uses: cachix/cachix-action@v16 with: name: project-template authToken: ${{ github.ref == 'refs/heads/main' && secrets.CACHIX_AUTH_TOKEN }} skipPush: ${{ github.ref != 'refs/heads/main' }}
So: on
main, push if the token is set; on PRs or other branches, only pull. -
Install devenv and run your pipeline (e.g.
devenv shell -- moon run :ci-formatand the rest of your checks).
Add the Cachix auth token as a repository secret (CACHIX_AUTH_TOKEN) if you want CI to push to your cache. If you only want to pull (e.g. from a public cache), you can omit the token and always use skipPush: true.
git clone --recurse-submodules <repo-url>
cd <repo>- The
.cursor/agency-agentsdirectory is a git submodule;--recurse-submodulespulls it. Without it, rungit submodule update --initlater.
devenv shellOn enter, devenv runs:
- prek-install — Installs git hooks from
.pre-commit-config.yaml(pre-push, commit-msg) into.git/hooks. Overwrites existing hook files so the repo’s config is always in use. - moon-sync — Runs
moon syncso moon’s toolchain and project graph are up to date. - sccache — Ensures
$HOME/.cache/sccacheexists so the Rust compiler cache can be used.
You get:
- Rust (stable), clippy, rustfmt, rust-analyzer, cargo-nextest, cargo-llvm-cov, cargo-audit
- Bun + TypeScript
- moon, treefmt, and all formatters (alejandra, beautysh, biome, taplo, yamlfmt, etc.)
- prek, git, gh, direnv
- Env:
RUST_BACKTRACE=1,CARGO_TERM_COLOR=always,RUSTC_WRAPPER=sccache,MOON_TOOLCHAIN_FORCE_GLOBALS=rust - Scripts:
prek-install,moon-sync,pre-push(used by the pre-push hook)
Optional: if you use direnv, direnv allow in the repo will enter the devenv shell automatically when you cd in.
- Format:
moon run :format(writes changes) ormoon run :ci-format(CI-style; fails if anything would change). - Check / lint / build / test:
moon run :check,:lint,:build,:test
Or run the full gate:
devenv shell -- pre-push(same as the pre-push hook). - Fix auto-fixable issues:
moon run :fix - Docs:
moon run :docsor:check-docs - Security:
moon run :audit; runcargo deny checkmanually for full deny checks. - Coverage:
moon run :coverage(nextest under llvm-cov).
All of these use the tools and configs from the dev shell (rustfmt, nextest.toml, etc.).
- When you run
git commit, the commit-msg hook (prek + commitizen) runs. - It validates that the commit message follows conventional commits (e.g.
feat: add X,fix: Y). If not, the commit is rejected.
- When you run
git push, the pre-push hook runs. - The hook runs:
devenv shell -- pre-push - pre-push (defined in
devenv.nixscripts) runs:
moon run :format :check :lint :build :test :audit :check-docs - If any of these fail, the push is aborted. So the branch you push has already been formatted, checked, linted, built, tested, audited, and doc-checked in the same way as in CI.
- In GitHub Actions (or similar), use the same commands inside a Nix/devenv setup so CI matches local and pre-push.
- Format check:
devenv shell -- moon run :ci-format
Usestreefmt.ci.toml(same as treefmt butfail-on-change = true). - Full pipeline:
devenv shell -- moon run :format :check :lint :build :test :audit :check-docs
Or usedevenv shell -- pre-pushto mirror the pre-push hook exactly.
| Task | What it does |
|---|---|
:format |
treefmt (format tracked files) |
:ci-format |
treefmt with treefmt.ci.toml (fail if not formatted) |
:check |
cargo check --workspace --all-features |
:lint |
cargo fmt --check + cargo clippy |
:build |
cargo build |
:test |
cargo nextest run |
:docs |
cargo doc --no-deps --all-features |
:fix |
cargo fix + clippy --fix |
:bench |
cargo bench (no-op if no benches/) |
:audit |
cargo audit |
:coverage |
cargo llvm-cov nextest |
:check-docs |
cargo doc + clippy with missing_docs lint |
Run with: moon run :<task> or devenv shell -- moon run :<task> from outside the shell.
- devenv.nix — Dev shell: packages, env, scripts (prek-install, moon-sync, pre-push).
- devenv.yaml — Nix flake inputs (fenix, nixpkgs, rust-overlay, etc.).
- moon.yml — Moon project and task definitions.
- treefmt.toml — Formatters and globs (local format;
walk = "git"). - treefmt.ci.toml — Same as above with
fail-on-change = truefor CI. - rustfmt.toml — Rust format options (edition 2024, 2 spaces).
- deny.toml — cargo-deny: advisories, licenses, bans.
- nextest.toml — nextest profiles (default + ci), timeouts, cache.
- .pre-commit-config.yaml — prek: pre-push (devenv pre-push script), commit-msg (commitizen).
- Clone (with submodules) → devenv shell → prek installs hooks, moon syncs, sccache ready.
- Develop with moon tasks (
:format,:check,:lint,:build,:test, etc.). - Commit → commitizen checks message.
- Push → pre-push runs full moon pipeline; push only if it passes.
- CI (when added) runs the same commands (e.g.
:ci-formatand the same full pipeline) inside devenv so results match local and pre-push.