Skip to content

Contributing

Thank you for helping. Bug reports, questions, docs fixes, examples and engine changes are all welcome. yen is small, and every page of its docs is a tested example, so a good report or a good example helps everyone after you.

An issue must have a yen module that shows the problem. The issue form asks for one. This is not a formality: CI evaluates every nix block of the issue as one fleet and comments the answer, often before a person reads it.

Each block is one module of modules/. Declare the kinds it needs; lib is nixpkgs lib. For example:

issue/block-1.nix
# The fleet: a root kind, and a host kind with an `admin` sink that takes one element.
{ yen, ... }:
{
yen.kinds.fleet.spawns.host = _: yen.host;
yen.kinds.host.sinks.admin.reduce = yen.lib.reduce.one;
yen.fleet.main = { };
yen.host.web = { };
}
issue/block-2.nix
# The problem: two aspects give an admin. I expected the second to win.
{ yen, ... }:
{
yen.aspects.ops = {
at = yen.kinds.host;
admin = "ana";
};
yen.aspects.dev = {
at = yen.host.web;
admin = "bo";
};
}

CI replies with the errors, the scopes, the active aspects and the invariant check:

result
{
active = {
dev = [
"fleet:main/host:web"
];
ops = [
"fleet:main/host:web"
];
};
check = {
incomplete = [ ];
ok = true;
violations = [ ];
};
errors = [
{
class = "fixable";
code = "one";
error = "one: got 2 elements";
from = "fleet:main/host:web";
got = 2;
path = [ ];
sink = "admin";
want = 1;
}
];
modules = 2;
scopes = [
"fleet:main"
"fleet:main/host:web"
];
undefined = [ ];
}

Here the answer is in the reply: a one sink with two elements is an error, not an override. The evaluation is pure and restricted (--restrict-eval): a block cannot read files outside its inputs, and it has a time limit. Run the same evaluation before you post, from the repository root, with your blocks in a directory as block-1.nix, block-2.nix:

Terminal window
nix-instantiate --eval --strict --json --restrict-eval -I yen=. -I nixpkgs=channel:nixos-25.05 -I blocks=./my-issue \
--expr 'import ./docs/issue/eval.nix { yen = <yen>; nixpkgs = <nixpkgs>; blocks = <blocks>; }'

Send a pull request: exercise the examples

Section titled “Send a pull request: exercise the examples”

The best pull request changes or adds an example under docs/examples/. An example is a page, a flake template and a test at once.

  1. For a bug, add a check to the page.nix of the closest example, or a variants/ file that shows the bug. Run docs-test: it fails.
  2. Fix the engine (default.nix, lib/) or the example. Run docs-test again: it passes.
  3. For a feature, add a page: copy an example directory, edit page.nix and modules/, and give it checks.
  4. Run docs-write to regenerate the pages, then ci for every stage.

A change to the engine also needs a test in tests/default.nix. Keep the engine to Nix builtins: no nixpkgs, no throw.

nix develop ./docs from the repository root (or nix develop in docs/) gives a devshell with every command below. menu lists them. Each command runs from the repository root, whatever your directory.

CommandGroupWhat it does
cicirun the CI stages locally, in order: fmt, engine, docs, fleet (fleet-check and the per-arch evals not included)
test-allcievery *-test command: engine, docs, fleet (no e2e, no cost)
check-allcievery *-check command: fmt, docs, fleet
docs-writedocswrite the pages and the sidebar from examples/
docs-testdocsthe checks of every example (fast)
docs-e2edocsreal NixOS, nix-darwin and home-manager evaluations of the examples
docs-checkdocsnix flake check of docs/: pages current, examples, e2e
docs-devdocsthe site at localhost:4321, live
docs-builddocsbuild the site to docs/dist
engine-testenginethe engine and library oracles
engine-costenginethe memo cost oracles
each-flakeflakerun command in every flake.nix directory that has it
fleet-testfleetthe reference fleet’s checks
fleet-e2efleetthe reference fleet’s real evaluations
fleet-costfleetthe trimmed-module-list cost oracle
fleet-mcpfleetthe MCP server tests
fleet-checkfleetnix flake check of the fleet (VM tests are vmTests, not checks)
fleet-evalfleetfleet-eval SYSTEM: every ci.json target of SYSTEM evaluates to a derivation
fleet-writefleetregenerate the fleet’s README.md and ci.json
fleet-flakefleetregenerate the fleet’s flake.nix
fmt-checkfmtevery tracked nix file is formatted (nixfmt), as CI checks
lintfmtthe workflows are valid (actionlint, with shellcheck)
fmtfmtformat every tracked nix file (nixfmt)
issue-evalissueissue-eval BODY DIR: an issue’s nix blocks as one fleet, restricted; writes DIR/comment.md
issue-commentissueissue-comment NUMBER FILE: post FILE on issue NUMBER, replacing yen’s last comment (needs GH_TOKEN)

Do not edit generated files. Edit their source, then run the generator. A check fails when a committed file is stale.

FilesSourceGenerator
docs/src/content/docs/**, docs/src/sidebar.jsondocs/examples/*/page.nixdocs-write
.github/workflows/test.ymldocs/modules/ci.nixdocs-write
.github/workflows/issue.yml, .github/ISSUE_TEMPLATE/*docs/modules/issues.nixdocs-write
CONTRIBUTING.mddocs/examples/contributing/page.nixdocs-write
templates/fleet/README.md, templates/fleet/ci.jsontemplates/fleet/modules/docs/fleet-write
templates/fleet/flake.nixthe flake-file sink of the fleetfleet-flake

CI runs in stages, one after another, so a push does not start every job at once: fmt, engine, docs, fleet, then flake-check and one evaluation job per system. ci in the devshell runs the same stages locally, except the last.

  • Update inputs. Run nix flake update in docs/ and in templates/fleet/, then ci. nixpkgs comes from the nixos.org channel tarballs, not from GitHub.
  • After a nixpkgs update, the fleet’s trimmed module lists belong to the old revision: fleet-e2e fails on modlist-same-drv until they are computed again.
  • Format with fmt; CI checks it with fmt-check.
  • A new error code in the engine fails the docs until the Error codes page (docs/examples/errors-reference) explains it.
Contribute Community Sponsor