Skip to content

rhiza-claude

A Claude Code plugin marketplace providing the rhiza plugin — slash commands for working in rhiza-managed repos: template sync, code-quality scoring, and README/doc upkeep.

The problem it solves

Every repo you own needs roughly the same scaffolding: CI workflows, a Makefile, lint and type and coverage gates, a docs build. Copy that into ten repos and you have ten copies drifting apart — a workflow fixed in one, a gate loosened in another, and no way to tell which repo has which vintage.

rhiza is the shared original: one multi-language template repository (jebel-quant/rhiza, for Python, Rust and Go alike) holding that scaffolding once. rhiza-claude — this plugin — is how a repo adopts it, keeps up with it, and gets told how it's doing: it syncs your repo from a pinned template release and scores the result.

Python is the fully-supported axis

All three languages sync, update, release and report the same way. What differs is the far end: Rust and Go have no hosted CI workflows yet, and /rhiza:quality's gate list is the Python profile, so on a Rust or Go repo it scores the targets it discovers in your Makefile and marks language-specific subcategories out-of-scope. A worked first run below is Python; see Language support for the full comparison.

The two-repo model

Two repositories, and the boundary between them is the whole idea:

owns who changes it
the template CI stubs, Makefile, the docs base upstream — you receive it
your repo source, tests, pyproject.toml, README prose you — the sync never touches it

A sync is therefore not a copy of the template over your repo. /rhiza:update writes only template-owned paths, and it knows which those are because the last sync recorded them. Your src/ is never in that set, so it can never be swept in — there is no blanket git add --all anywhere in the flow.

What "rhiza-managed" means

Two files under .rhiza/, and they answer different questions:

file it is question it answers
.rhiza/template.yml a pointer — template repo + pinned ref what would we sync from?
.rhiza/template.lock a record — repo, ref, commit SHA, timestamp, strategy, every managed file what was delivered, and when?

Intent versus outcome. They can disagree in both directions: a freshly /init-ed repo has a valid pointer and no lock at all, and a long-synced repo can have a lock beside a pointer someone has since broken by hand. That's why /rhiza:status reports both halves, and why /rhiza:quality checks for both before it runs: every gate it scores is one the sync delivers, so scoring an unsynced repo would report it as broken rather than as unsynced. It asks the record, not any file the sync wrote — the second probe used to be .rhiza/rhiza.mk, and when template v1.4 stopped shipping it, every fully synced repo on that template was told it had never been synced. (By contrast /rhiza:release requires neither — it reads the repo's own [tool.bumpversion] config, so it works on any git repo.)

The intended path

flowchart LR
    A["empty or existing repo"] -->|"/rhiza:init"| B["PR: .rhiza/template.yml<br/>+ skeleton + LICENSE"]
    B -->|"you merge"| C["rhiza-managed<br/>(no lock yet)"]
    C -->|"/rhiza:update"| D["PR: template-owned files<br/>+ .rhiza/template.lock"]
    D -->|"you merge"| E["managed and synced"]
    E -->|"/rhiza:quality"| F["scorecard + findings"]

Three commands, two PRs you merge yourself, in that order:

  1. /rhiza:init writes exactly one file of its own — .rhiza/template.yml — and deliberately syncs nothing. No CI, no Makefile, no gates arrive here.
  2. /rhiza:update performs the first sync, which is what brings the template content in and writes .rhiza/template.lock.
  3. /rhiza:quality needs that lock's worth of content to exist, so it comes third.

Running them out of order isn't dangerous, it just stops early and says so — but the sequence is worth knowing before you start, because "/init and nothing happened" is the expected outcome of step 1, not a failure.

Commands and procedures

The plugin ships two kinds of markdown, and the difference is enforced rather than conventional:

  • skills/<name>/SKILL.md → slash commands you invoke: ten of them, each with a page here. The directory carries the command name, so skills/init/SKILL.md is the file that answers /rhiza:init.
  • prompts/*.md → internal procedures: eight shared steps a command reaches with the Read tool, deliberately kept outside skills/ so they cannot be invoked directly.

That second point is the load-bearing one. Claude Code finds components by scanning particular directory names at the plugin root — skills/ and hooks/ are two of them, and this plugin ships both. prompts/ is deliberately not one, which is the guarantee: a procedure kept there cannot be reached as a slash command.

Procedures are not implementation trivia — they're where the shared behaviour lives, so skills/init/SKILL.md alone doesn't explain what /rhiza:init does. Installing uv, choosing a work branch, scaffolding a pyproject.toml, pinning a Python version, writing a licence, gathering design evidence, applying the scoring rubric, recording which upstream failures are known: all of that is a procedure, which is exactly why /init and /update behave identically where they overlap. Nor can they be folded into the skills that read them — pr-base is read by three commands and install-uv by two, so a shared procedure has no single skill folder to live in. They're documented under Internals below.

Why the commands are prose

Because the split is deliberate: deterministic work belongs in tested code, judgement belongs in markdown.

The bundled Python under scripts/ does the parts with one right answer — parsing the lock, merging synced files, comparing versions, computing candidate bumps — and is stdlib-only, type-checked and covered by tests. The markdown does the parts that need a reading of your repo: which findings matter, whether a breaking change should spend the 1.0 signal, what to preserve in a README someone wrote by hand.

That's also why the prose itself is gated in CI: a command that names a script, a flag or another command that no longer exists fails the build rather than failing in front of a user mid-task.

What it needs

Honest scope: uv is the one hard dependency, and both /init and /update offer to install it as their first step. git and make are used and are near-universal. The plugin's own scripts are stdlib-only Python — there is no rhiza CLI to install, which is the most common point of confusion about what this needs.

It also means the deterministic half runs without any of the rest: if you want the sync, the status read or the bootstrap from a shell script or a CI job with no LLM in the loop, see Without Claude Code.

Language support

/rhiza:init offers Python, Go and Rust, and all three point at the same multi-language template — the language selects a profile (github-project, rust-local, go-local), never a different repository. They are not equally supported, and the difference is worth knowing before the bootstrap, not after.

Python Rust Go
/init, /update, /status, /release, /detach
Local toolchain from the template ✅ cargo, clippy, nextest, llvm-cov, cargo-deny ✅ go test, golangci-lint, govulncheck, revive
Hosted CI workflows ❌ none yet ❌ none yet
/quality gate list ✅ known and named ⚠️ discovered at runtime ⚠️ discovered at runtime
Test-layout parity subcategory n/a n/a

Python is the fully-supported axis. /rhiza:quality's gate list is the Python profile — the one this plugin has actually run against. On a Rust or Go repo it probes the Makefile with check_make_targets.py, scores the targets it discovers, and marks language-specific subcategories out-of-scope.

That is a deliberate trade. A hand-written table of targets for templates the plugin has never run against would be prose asserting things it cannot back; discovery degrades honestly where a guessed table would lie. The cost is that a Rust or Go scorecard rests on a narrower base than a Python one — so /quality states in its own output which gates it discovered and which subcategories it skipped.

There is deliberately no rust-github-project or go-github-project profile: those are almost entirely CI workflows, and rhiza's github/gitlab bundles still ship Python ones. Until those land, add hosted CI yourself.

The worked first run below is Python.

Install

/plugin marketplace add Jebel-Quant/rhiza-claude
/plugin install rhiza@rhiza-claude

Or, from a shell:

make install

The commands appear namespaced under the plugin — type /rhiza to have Claude Code autocomplete them.

First run, start to finish

An empty directory to a managed, synced, scored repo. Two PRs, both merged by you.

0. A git repo with a remote

mkdir my-lib && cd my-lib && git init
gh repo create my-lib --private --source=. --push   # or create it in the UI

/init never pushes to your default branch, so if the repo doesn't exist upstream yet it asks you to create it (an empty README is enough) rather than pushing one itself. Then start Claude Code in that directory.

1. /rhiza:init — become rhiza-managed

/rhiza:init

It detects platform, owner and name from origin (asking when it can't), picks the language and template repo, resolves the template's latest release as the initial pin, and opens a PR on a rhiza_init_<date> branch containing:

written by file
the command itself .rhiza/template.yml — the pointer and pinned ref
the skeleton procedure pyproject.toml (via uv init --lib), src/, .python-version
the license procedure LICENSE + the SPDX metadata in pyproject.toml

No CI, no Makefile, no .rhiza/template.lock. /init runs no sync and no gates by design — the template content is a separate PR, so the two are reviewable apart. If you expected workflows to appear here, nothing has gone wrong.

Merge that PR.

2. /rhiza:update — the first sync

/rhiza:update

This is the step that brings the template in. It bumps the ref in template.yml to the newest template release, syncs, resolves any conflict by taking the upstream side, and opens a second PR containing the template-owned files — .github/workflows/, Makefile, the docs base — plus .rhiza/template.lock recording exactly what was delivered.

Only paths the lock names are staged, so nothing of yours is included even if it changed in your working tree. Merge that PR too.

3. /rhiza:status — confirm both halves

/rhiza:status --files

Read-only. It validates the pointer and prints what the lock records: template repo, ref, commit SHA, timestamp, strategy, and — with --files — the managed files as a tree. This is how you tell "managed and synced" from "managed but never synced".

4. /rhiza:quality — get scored

/rhiza:quality

Now the gates exist as real make targets, so this works: lint, types, docs, deps, security, tests, complexity, architecture → a 1–10 scorecard with findings, and an optional menu to file them as issues. It proposes fixes but applies none.

Afterwards

  • /rhiza:update again whenever the template cuts a release (/rhiza:status --check compares your pin against the latest).
  • /rhiza:docs to create or refresh README.md, CLAUDE.md and mkdocs.yml — it preserves hand-written prose.
  • /rhiza:remote once a fix is open as a request — it reads what CI on the origin said and fixes the red ones. Needs no .rhiza/ either.
  • /rhiza:release when you want to cut a version. It needs no .rhiza/ at all.

Commands

These are the AI-driven workflow commands. Each has its own page.

Command What it does
/rhiza:init Make the repo rhiza-managed: write .rhiza/template.yml, delegate the skeleton + license, open a PR.
/rhiza:update Sync to the latest template release and open a PR with only template-owned files.
/rhiza:quality Run the code-quality gate and score the repo 1–10 across eight categories.
/rhiza:docs Create or refresh README.md, CLAUDE.md, and mkdocs.yml.
/rhiza:release Release by PR: pick the next version from a table, bump, changelog, open the PR — then tag the merged commit on a second run.
/rhiza:remote Read what CI on the origin said about the open requests, then diagnose and fix the red ones on their own branches.

Repo utilities

Thin, read-only, stdlib-only commands backed by bundled scripts — they read .rhiza/template.lock / .rhiza/template.yml directly and work without the rhiza CLI installed. Neither writes anything.

Command What it does
/rhiza:status Report both halves of the repo's rhiza state: is template.yml valid, and what did the last sync record. --files lists managed files as a tree; --check compares the pinned ref against the latest release.
/rhiza:maffay Return a bonmot from a random Peter Maffay song. Takes an optional theme keyword. Needs no repo at all.

Machine setup

The one command that writes outside a repo, and so the one to run once per machine rather than once per project.

Command What it does
/rhiza:completions Install make tab-completion for bash, zsh or both, into ${XDG_DATA_HOME:-$HOME/.local/share}. Generic make completion, so it works in every project on the machine; refuses to overwrite a completion it did not write unless --force is passed.

Destructive

Command What it does
/rhiza:detach Detach the repo from rhiza: delete every rhiza-managed file listed in .rhiza/template.lock, prune the emptied directories, and remove the lock. Prompts for confirmation unless --force is passed. Detaches a repo, not the plugin.

Internals

Not slash commands. These are internal procedures in the plugin's prompts/ directory — deliberately outside skills/ so they can't be invoked directly. /rhiza:init and /rhiza:update read and follow them, and most are backed by a deterministic, stdlib-only script under scripts/. Documented here because their behaviour is part of what those commands do to your repo.

Procedure What it does Script
install-uv Make sure uv is installed — the first step of both /init and /update.
pr-base A work branch off an up-to-date default, which is never pushed to.
skeleton uv init --lib, then the [project] shape the template's gates require. init_skeleton.py
license SPDX license/license-files metadata + the LICENSE file. set_license.py
python-version Pin requires-python, rewrite the Python classifiers, sync .python-version. set_python_version.py
design-analysis Complexity and architecture evidence that no make gate measures.
scorecard The 1–10 rubric, the scoping rule, findings and the issue menu.

The long form

Everything above, at length and with worked examples: An introduction to rhiza-claude (PDF).

It covers the two-repository boundary a sync respects, what "rhiza-managed" means on disk, installation, a worked first run from an empty directory to a scored repository, why the commands are prose rather than code, and how the design compares against shipping an MCP server. The figures in it are captured command output, not mock-ups.

The PDF is rebuilt from paper/rhiza-claude-intro.tex on every commit, so it never lags the plugin it describes.