5. Separate rhiza Template Repository from rhiza-cli¶
Date: 2024-05-01
Status¶
Accepted. Amended 2026-08-22: the engine half is now the rhiza Claude Code plugin
rather than the rhiza-cli PyPI package — see Amendment: the engine moved at the end
of this record. The decision this ADR made, content separated from engine, is unchanged.
Context¶
The initial Rhiza concept combined two concerns in a single repository: the curated collection of template files that downstream projects receive, and the CLI tooling that fetches and applies those files. As the project matured, keeping these concerns together created friction:
- Tight coupling: Any change to the CLI logic required a release of the entire template repository, even if no template files had changed (and vice versa).
- Version confusion: Downstream projects pin a specific version of Rhiza in their
.rhiza/template.yml. If CLI behaviour and template content share the same version number, a CLI bug fix forces a bump that causes Renovate to open unnecessary sync PRs. - Install overhead: The CLI is invoked ephemerally via
uvx rhiza(no persistent install). Bundling it with the template repository would require downloading the full template content just to run CLI commands. - Discoverability: Packaging the CLI separately on PyPI (
rhiza-cli) makes it straightforward for developers to discover and install the tool independently of any particular template version. - Custom template repositories: A core Rhiza use case is allowing teams to fork the template repository and maintain their own variant. These forks should not need to also fork or maintain the CLI code.
Decision¶
We will maintain two distinct components:
- rhiza (this repository) — the template
content: the curated set of configuration files, Makefile modules, CI/CD workflows,
and tooling files that downstream projects sync from. Versioned with Git tags (e.g.,
v0.7.1). - rhiza-cli — the CLI engine: a separate
Python package published to PyPI, invoked ephemerally via
uvx rhiza. Implements theinit,sync,bump, andreleaseoperations.
Interaction model:
- Downstream projects specify the template source and version in
.rhiza/template.yml(repository: Jebel-Quant/rhiza,ref: v0.7.1). - When
uvx rhiza syncis run,rhiza-clireads this file, fetches the matching files from the specified repository at the given ref, and applies them to the project. - The CLI version and the template version are independent; either can be updated without forcing an update of the other.
Custom template repositories:
Because the CLI is decoupled, teams can set repository to any GitHub repository
(their own fork or an entirely independent template) without needing to ship their own
CLI. The CLI is generic; the template content is specific.
Consequences¶
Positive¶
- Independent release cadence: Template improvements and CLI bug fixes can be released independently, without coupling.
- Smaller surface area: Downstream projects only interact with template files; they never need to understand CLI internals.
- Fork-friendly: Teams can create their own template repository and point
rhiza-cliat it without forking the CLI. - Ephemeral invocation:
uvx rhizadownloads and caches the CLI on first use with no persistent installation required. - PyPI discoverability:
pip install rhiza-clioruvx rhizais a familiar, standard pattern for Python developers.
Neutral¶
- Two repositories to follow: Contributors interested in the full system need to track both repositories. The README clearly documents this two-component model.
- Version coordination: Occasionally, a new CLI feature requires a minimum template version (or vice versa). Compatibility constraints are documented in release notes.
Negative¶
- Distributed codebase: Bugs that span both components (e.g., a sync issue caused by CLI logic interacting with a specific template structure) require debugging across two repositories.
Amendment: the engine moved (2026-08-22)¶
rhiza-cli is unpublished and its repository
is archived. The engine role it filled belongs to
rhiza-claude, a Claude Code plugin whose
/rhiza:update reads .rhiza/template.yml, fetches the selected bundles at the pinned
ref, three-way merges them into the working tree and records what arrived in
.rhiza/template.lock. That sync is stdlib-only Python shipped inside the plugin, so
there is no package to install and no uvx rhiza entry point at all.
Every property the decision rests on survives the substitution, which is why this is an amendment and not a superseding ADR:
- Two components, versioned independently. Template tags here, plugin releases there.
- Any repository can be the template.
repository:is still read from the consumer's own config, so a variant is pointed at rather than forked into. - Nothing of the engine is synced. A plugin install is the modern form of the
no-persistent-installation property
uvxprovided.
Two claims in the record above no longer hold as written:
- PyPI discoverability is gone, because the engine is not a PyPI package. The four
operations went to three places:
initandsyncare/rhiza:initand/rhiza:update;bumpandreleaseare/rhiza:releasedriving bump-my-version andrhiza_release.yml; and the developer tasks that were never this CLI's —install,test, every gate — are rhiza-task, pinned asRHIZA_TASKin the syncedMakefile, with the conformance checks in pytest-rhiza. - "Two repositories to follow" is now five. The boundary held; it multiplied. The README's companions table is the current map.