Skip to content

Contributing

This page is for contributors working on Genja itself. If you only need to add Genja to an application, start with Installation and Quickstart.

Development Setup

Genja is a Rust workspace with Python bindings for the core runtime. A full development environment needs:

  • Rust and Cargo
  • Python 3.10 or newer
  • PDM for the Python binding development environment
  • maturin for building the Python extension module
  • Zensical for building the documentation site

Tool Versions

The workspace pins Rust and Cargo to 1.88.0 in rust-toolchain.toml. Use that toolchain locally so compiler diagnostics, formatting, and trybuild snapshots match CI.

Individual crates do not currently declare a formal rust-version MSRV in their package metadata.

Python binding development requires Python 3.10 or newer, as declared by the genja-py package.

Clone the repository and check the Rust workspace first:

cargo check --workspace

For Python binding work, install the Python development dependencies from the binding package:

cd genja-core-python
pdm install

VS Code And Rust Analyzer

Optional Feature Analysis

Rust Analyzer normally analyses the default Cargo features. Code behind a feature gate, such as genja-cli's tui module, can be treated as inactive unless that feature is enabled for the editor. The CLI's commands, discovery, and output modules are not feature-gated within genja-cli, so they do not need the same configuration.

For TUI work, add this to your private VS Code workspace settings, preserving any other features you already enable:

{
  "rust-analyzer.cargo.features": ["genja-cli/tui"]
}

Alternatively, use "rust-analyzer.cargo.features": "all" to analyse all optional features. This can increase analysis time and compile additional dependencies. These editor settings do not change the crate's default features or enable features for separate Cargo commands you run in a terminal.

Reload the Rust Analyzer workspace after changing the configuration.

Inline Type Hints

Inlay type hints display inferred types alongside variables in the editor; they are not part of the source code. If hover and autocomplete work but inline hints are missing, enable inlay hints in VS Code's Settings UI and check this Rust Analyzer setting:

{
  "rust-analyzer.inlayHints.typeHints.enable": true
}

Showing inline hints is an editor preference, separate from enabling feature analysis. Enabling hints alone does not activate optional Cargo modules. Explicitly typed declarations may not need an inferred type hint; hover can still show their type information. See the Rust Analyzer configuration reference for available hint settings.

Personal Editor Settings

Keep personal settings in VS Code's user settings or an untracked workspace configuration. The examples above are optional contributor setup; they do not require adding .vscode/settings.json to the repository. Agree on shared editor settings with maintainers before committing them.

Rust Checks

Run the Rust workspace tests before sending changes that affect Rust behavior. Exclude genja-core-python from the workspace test run; its PyO3 tests depend on the Python environment and should be run with the PDM commands below.

cargo test --workspace --exclude genja-core-python --all-features

--all-features includes optional feature-gated tests, such as discovery through genja::cli. Without it, Cargo enables only default features; selecting the genja-cli workspace member does not enable the genja-cli feature on genja.

Also check that genja builds without optional features, as CI does:

cargo check -p genja --no-default-features

For focused changes, run the package that owns the behavior:

cargo test -p genja-core
cargo test -p genja
cargo test -p genja --features genja-cli
cargo test -p genja-cli
cargo test -p genja-core-derive
cargo test -p genja-plugin-manager

Use Cargo formatting before submitting Rust changes:

cargo fmt --all -- --check

This CI command is read-only. It fails when formatting changes are needed; fix those locally with:

cargo fmt --all

Run Clippy with warnings treated as errors:

cargo clippy --workspace --all-targets --all-features -- -D warnings

CI does not auto-fix formatting or Clippy warnings. If you use cargo clippy --fix, review the generated changes and commit the ones you want to keep.

If a change touches shared runtime behavior, task execution, plugin loading, or public APIs, prefer running both the focused package tests and the Rust workspace test command above.

Trybuild UI Fixtures

The derive macro compile-fail tests under genja-core/tests/ui_genja_task/ use trybuild .stderr snapshot files.

These snapshots are sensitive to Rust compiler diagnostic formatting. In practice, drift is usually caused by rustc version changes rather than by the feature change itself.

When a trybuild test fails only because the expected and actual diagnostics have drifted, refresh the fixtures with:

TRYBUILD=overwrite cargo test -p genja-core --test derive_compile

Do not hand-edit .stderr fixtures unless you are making a small targeted correction. Prefer regenerating them from compiler output.

Refresh trybuild snapshots only when:

  • macro diagnostics intentionally changed
  • the Rust toolchain changed and the emitted diagnostics legitimately drifted

When adding new compile-fail fixtures, minimize unrelated warnings in the test case where possible so snapshot files are less brittle across toolchain updates.

Python Checks

The Python bindings live in genja-core-python. Run commands from that directory.

Install the Python development dependencies before running checks:

pdm install -d

Build the extension into the active Python environment and run the Python tests:

pdm run test

Run the Rust tests that exercise the PyO3 crate:

pdm run test-rust

Use pdm run test-rust instead of invoking cargo test -p genja-core-python directly. The wrapper runs Cargo through the project Python environment, sets the correct PYO3_PYTHON interpreter, and ensures the PyO3-backed tests can access the installed Python packages.

Avoid launching multiple genja-core-python Rust-backed test commands concurrently from separate processes. Each run is already single-threaded internally, and concurrent invocations may hang or fail to return output reliably in this workspace.

Run type checks for the Python examples and tests:

pdm run typecheck

Check Python API stub and PyO3 documentation coverage:

pdm run check-stubs

Run this check when changing Rust/PyO3-exposed Python APIs, .pyi stubs, Python API docstrings, or top-level Python re-exports. It verifies that the top-level python/genja/*.pyi stubs are listed for docstring checks, public stub APIs have docstrings, duplicated top-level stubs stay aligned with genja.pyi, and documented PyO3 bindings do not regress.

When adding a top-level python/genja/*.pyi stub, document its public API and add it to STUBS_REQUIRING_DOCSTRINGS in genja-core-python/scripts/check_python_api_docs.py during the same change. When adding Rust/PyO3 doc comments for another binding source file, extend the same script so CI prevents future regressions for that file too.

For Rust/PyO3 classes re-exported from genja/__init__.py, keep the class shape aligned between genja.pyi and __init__.pyi. If another duplicated top-level class is brought under the documentation standard, add its name to DUPLICATED_TOP_LEVEL_CLASSES in check_python_api_docs.py. The check compares public methods and properties so the top-level import surface does not drift from the extension-module stub.

Run Python lint and formatting checks:

pdm run lint

When debugging PyO3 test failures, the project also provides:

pdm run test-rust-debug

Changelog And Compatibility

This repository uses a single top-level CHANGELOG.md. This repository follows Keep a Changelog style for CHANGELOG.md.

  • Add user-visible changes under Unreleased.
  • Use release-note headings such as:
  • Added
  • Changed
  • Fixed
  • Removed
  • Mark breaking entries inline with **Breaking:** and include migration guidance in the same bullet.
  • Update the changelog during the branch, not only at merge time.
  • For linked work items, use Refs: #<issue> when an issue exists.

Documentation

Documentation pages live in docs/ and the navigation is configured in zensical.toml.

After changing documentation, build the site:

zensical build

To preview the site locally:

zensical serve

When adding a new guide, update both the navigation in zensical.toml and the link list in Home when the page should be discoverable from the front page.

Examples

Rust examples live under genja/examples. Run them with Cargo:

cargo run -p genja --example basic_runtime
cargo run -p genja --example filter_hosts
cargo run -p genja --example run_task
cargo run -p genja --example run_task_tree
cargo run -p genja --example async_inventory_plugin

Python examples live under genja/examples/python. Build the local Python extension before running them:

cd genja-core-python
maturin develop
cd ..
python genja/examples/python/basic_runtime.py

If you add or change an example, update Examples so users know what the example demonstrates.

Plugin Changes

Plugin manager changes often need dynamic loading coverage. The workspace includes test plugin crates under genja-plugin-manager/tests/.

For plugin loading, registration, ABI, or manager behavior, run:

cargo test -p genja-plugin-manager

For runtime behavior that crosses from plugins into task execution, also run the relevant genja-core or genja tests.

Contribution Flow

Keep changes focused. A good contribution usually includes:

  • The implementation change
  • Tests for changed behavior
  • Documentation updates for user-facing behavior
  • Example updates when the change affects common workflows

Before opening a pull request, run the smallest focused checks that prove the change and the broader checks for any shared runtime or public API changes.

GitHub Actions runs CI for pull requests and for pushes to main, develop, feature/**, and issue-style *-genja-* branches. CI runs formatting checks, Clippy with all features and -D warnings, Rust tests with all features excluding genja-core-python, a separate genja build without optional features, and the Python binding lint, typecheck, Python test, and PyO3 Rust test commands. A separate cross-platform compatibility workflow runs on pull requests into main and checks Linux, macOS, and Windows across all supported Python versions.

Release publishing is separate from CI. Rust crates are published only from rs-vX.Y.Z tags on commits reachable from main, and the release workflow publishes the publishable Rust crates in dependency order after validating crate versions and internal dependency metadata. Genja currently uses a unified Rust release train: when preparing rs-vX.Y.Z, bump genja, genja-core, genja-core-derive, genja-plugin-manager, and genja-cli to X.Y.Z, even if one crate only changed because the release train moved. Keep internal path dependency version requirements aligned to the dependency crate's released version. The publish order is genja-core-derive, genja-core, genja-plugin-manager, genja-cli, then genja. This includes the optional CLI dependency: genja-cli must be available on crates.io before publishing genja.

Validate release metadata locally with python scripts/validate-release.py. During release preparation, pass --tag rs-vX.Y.Z to check the intended version. Package verification against crates.io requires internal dependency versions to be published first; the release workflow publishes and waits for each dependency before packaging the next crate.

Python package releases use matching py-vX.Y.Z tags. The release workflow checks the genja-py package version against the genja-core-python and genja-cli crate versions, and checks that the cli extra requires the matching minor release line. For v0.5.0, that requirement is genja-cli>=0.5.0,<0.6.0. Bump these versions together on the release branch. Update the examples in Versions And Compatibility with the released version at the same time. The workflow builds platform wheels for both packages, tests base and optional wheel-only installs, and publishes the CLI wheels before the dependent genja-py wheels and source distribution. Configure PyPI trusted publishing for both distributions before tagging the release. The CLI package has no source distribution; unsupported platforms cannot build it through pip. Confirm that the genja-cli PyPI project name can be claimed before release; if unavailable, rename the wheel distribution and the genja-py extra's dependency consistently.

Documentation package versions

The documentation package-version check is scoped to Markdown guides under docs/, the repository README.md, and the package-root READMEs for genja, genja-core, genja-core-derive, genja-plugin-manager, genja-cli, and genja-core-python.

After updating the release examples, run the checker from the repository root:

python scripts/check-doc-versions.py --version X.Y.Z

Use --tag rs-vX.Y.Z or --tag py-vX.Y.Z to validate a tag. The checker reports the file, line, found version, and expected version for each stale reference. CI runs it for release/ISSUE-vX.Y.Z branches, including SemVer pre-release suffixes such as release/163-v0.5.0-alpha.1, and before either publish workflow proceeds. A pre-release branch checks documentation against its base X.Y.Z version. To test the checker itself, run pdm run test-doc-versions from genja-core-python.

Installation commands, Cargo dependencies, compatibility tables, release examples, and prose describing the current Genja release must use the intended release version. A compatible minor-version range, such as a genja-py[cli] requirement, keeps its lower bound at the first patch release of that minor line and its upper bound at the next minor line; those bounds do not change for later patch releases.

Task IDs and task registration versions belong to task authors, so examples such as example.hello@1.0.0 are outside this check. IP addresses and other numbers unrelated to Genja package versions are also outside it. Historical statements, such as when a feature first appeared, may retain an older version with an explanation immediately before the reference:

<!-- genja-version-check: allow-next 0.5.0 reason="CLI support first appeared in this release" -->
The CLI feature became available in v0.5.0.

The allow-next comment exempts only the next recognized occurrence of the specified Genja package version within three lines, including one inside a following code block. It must include a reason. Repeat it for each intentional older occurrence; do not exclude a whole file. The checker rejects unused or mismatched comments so moved text cannot silently leave stale exceptions behind.

Issue Templates

Choose a release, feature, or bug template from the new-issue chooser. For a release, replace every X.Y.Z placeholder, remove steps that do not apply, and use the checkboxes to track preparation, validation, publication, and post-release verification. Issue templates become available in the chooser after they are merged into main.

Release Flow

Use develop as the integration branch and main as the release branch. Release preparation should happen on a branch named release/ISSUE-vX.Y.Z, where ISSUE is the release issue number, cut from develop. A SemVer pre-release suffix such as -alpha.1 or -rc.1 may follow X.Y.Z during preparation; the Rust and Python publication workflows currently require stable release tags:

feature/* -> PR -> develop
develop -> release/ISSUE-vX.Y.Z
release/ISSUE-vX.Y.Z -> PR -> main
main -> fast-forward develop

While the release pull request is open, avoid merging new work into develop so develop can be fast-forwarded to the released main commit after publication. If develop has moved ahead, do not force it; either finish the release sync first or use a normal merge from main into develop.

After the release pull request is merged, tag the exact resulting main commit. Rust and Python release tags may point at the same commit:

git checkout main
git pull origin main
git tag rs-vX.Y.Z
git tag py-vX.Y.Z
git push origin rs-vX.Y.Z py-vX.Y.Z

Fast-forward develop to the released commit before resuming feature merges:

git checkout develop
git pull origin develop
git merge --ff-only main
git push origin develop

Release Notes

User-facing compatibility information belongs in Versions And Compatibility. Maintainer-only publish steps should stay out of the user guides unless they are needed by contributors building release artifacts.

Contributor FAQ

Why are type hints or autocomplete missing in optional Rust modules?

Rust Analyzer may be analysing only default features. Enable the feature you are working on, such as genja-cli/tui. See Optional Feature Analysis for configuration and Inline Type Hints for hint display settings.

Should I commit my VS Code settings?

Keep personal settings private unless maintainers agree to share them. See Personal Editor Settings for where to keep them.