---
name: rhiza-init
description: Make the current folder a rhiza-managed repo — write the .rhiza/template.yml pointer, add a skeleton and license, open a PR. Supports Python, Rust and Go. Syncs nothing; run /rhiza:update after it merges.
---

> **Portable copy of `plugin/skills/init/SKILL.md`, generated by
> `plugin/scripts/build_bundle.py`. Edit the source and run `make bundle` —
> an edit here is overwritten.**
>
> - **`${RHIZA_ROOT}`** is your `rhiza-claude` checkout. Export it, or
>   substitute the path wherever it appears. Any remark below about the
>   variable being empty in a source checkout is Claude Code's spelling of the
>   same idea — `${RHIZA_ROOT}` replaces it, and the repo-relative fallbacks do
>   not apply, because you are working in the *user's* repo, not in this one.
> - **`/rhiza:<name>`** names another skill in this bundle, `rhiza-<name>`.
>   Invoke it however your client invokes skills.
> - **Tools.** `Read`, `Edit` and `Write` read and write files; `Grep` and
>   `Glob` search; `Bash` is a shell in the user's repo. Use your equivalents.
> - **`AskUserQuestion`** is a multiple-choice question put to the user. With no
>   such tool, ask in plain text, number the options, and **wait for a reply**:
>   where the procedure says nothing is created without an explicit selection,
>   that holds however the question was asked.
> - **Arguments:** [repo name]  (optional; defaults to the current folder name).
>   The text below writes `$ARGUMENTS` for what the user passed;
>   substitute it yourself if your client does not.
> - **Runs:** `git`, `gh`, `glab`, `uv`, `curl`, `brew`, `ls`, `basename`, `pwd`, `date`. Nothing here enforces that list, where the
>   plugin's frontmatter did — your client has to permit them.

You are running `/init` in the **current working directory**. Goal: make this folder
a **rhiza-managed** repo and deliver it as a **PR**.

**`/init` writes exactly one file itself** — `.rhiza/template.yml`, the pointer saying
which template repository this repo follows and at which ref. Everything else it
`Read`s from the procedure that owns it, so each concern has one source of truth and
`/init` stays a coordinator rather than a second implementation:

| what | procedure | step |
| --- | --- | --- |
| `uv` on the machine | `bundle/prompts/install-uv.md` | 1 |
| work branch off an untouched default | `bundle/prompts/pr-base.md` | 4 |
| skeleton + the `pyproject.toml` shape the gates need | `bundle/prompts/skeleton.md` (applies `bundle/prompts/python-version.md`) | 6 |
| SPDX metadata + the `LICENSE` file | `bundle/prompts/license.md` | 6 |

Those are **internal procedures, not slash commands** — deliberately kept out of
any directory Claude Code scans, so the user can't invoke them. `Read` each at the step that calls for it
(`${RHIZA_ROOT}/bundle/prompts/<name>.md`; in a source checkout the variable is
empty, so use the repo-relative path) and follow it as written. **Never re-implement
one inline** — no hand-rolled `pyproject.toml` edits, no `LICENSE` writing, no
`uv init` of your own.

**No sync, no gates.** Bootstrapping is two PRs: **#1 (`/init`) makes the repo
rhiza-managed; #2 (`/update`, after #1 merges) pulls the template content.** Keeping
the sync out of `/init` stops it re-implementing `/update` and drifting from it. The
user runs `/docs` for `README.md`/`CLAUDE.md`/`mkdocs.yml` and `/quality` for a
scorecard.

Argument (optional): `$ARGUMENTS` — the repository name; default `basename "$PWD"`.
Hold as `NAME`.

Work through these steps. Stop and report if a precondition fails.

## 1. Preconditions

- **Already rhiza-managed? → hand off.** If a `.rhiza/` directory exists
  (`test -d .rhiza`), **invoke the `update` command via the Skill tool** and stop.
  Don't write or touch anything under `.rhiza/` yourself — bumping an existing config
  is `/update`'s job. This holds even for a stray `.rhiza/` with no `template.yml`.
- **`uv`** — `Read` `bundle/prompts/install-uv.md` and follow it, every run. A one-line
  no-op when `uv` is present; otherwise it installs it. If `uv --version` still fails,
  stop.
- **Git** — `git rev-parse --is-inside-work-tree` (ignore the error if absent). No
  repo ⇒ `git init -b main`. If a repo exists, record any `origin` as
  `EXISTING_ORIGIN`.

You don't need to vet the folder's contents: everything `/init` runs is additive and
never overwrites, so an empty folder and a mature repo are the same case.

## 2. Platform, owner, name

> **If `EXISTING_ORIGIN` was found**, derive everything from that URL and **ask
> nothing**: platform from the host (`github.com` → GitHub/`github-project`; a GitLab
> host → GitLab/`gitlab-project`), `OWNER`/`NAME` from the path. Report what you
> detected and go to step 3.

Otherwise ask (`AskUserQuestion`): **platform** (GitHub first, marked
"(Recommended)"; GitLab second), **owner/namespace** (no safe default), **repository
name** (default `NAME`), **visibility** (private recommended).

Verify the platform CLI is authenticated — don't pick the binary yourself:
```bash
uv run --python 3.12 --no-project python "${RHIZA_ROOT}/plugin/scripts/platform_cli.py" \
  auth-status
```
Exit **0** is authenticated. Exit **1** means the CLI is absent or logged out; its note
says which. Tell the user the fix (`gh auth login` / `glab auth login`) — you may still
complete the local work and report that the remote steps are pending auth.

## 3. Template source and version

- **Language** — ask (`AskUserQuestion`, default **python**): `python`, `rust` or `go`.
  It picks the default template repo, the `language:` key in the pointer, and the
  profile: `python` gets `github-project`/`gitlab-project`, `rust` gets `rust-local` and
  `go` gets `go-local`, on either host.
- **`TEMPLATE_REPO`** — default `jebel-quant/rhiza` for **all three languages**: that
  template is multi-language, layering a per-language toolchain bundle (`python-core`,
  `rust-core`, `go-core`) on a neutral `core`. Offer to override with any `owner/repo`
  (a fork), or to pick from `gh search repos --topic rhiza --json fullName`.
- **Rust and Go get no hosted CI yet.** There is deliberately no `rust-github-project`
  or `go-github-project`: those profiles are almost entirely CI workflows, and rhiza's
  `github`/`gitlab` bundles still ship Python ones. **Say this to the user** when they
  pick either, so no one waits for CI that was never configured: they get the full local
  toolchain (cargo/clippy/nextest/llvm-cov/cargo-deny, or go test/golangci-lint/
  govulncheck/revive) and add hosted CI when those workflows land.
- **Reachability** — `git ls-remote --exit-code https://<host>/$TEMPLATE_REPO`. If
  unreachable, **stop** — don't write a pointer at a repo that isn't there. (If `git`
  can't check, warn and continue.)
- **`TARGET`** — its latest release:
  `gh release list -R "$TEMPLATE_REPO" -L 1 --json tagName --jq '.[0].tagName'` (fall
  back to `git ls-remote --tags` for a GitLab-hosted template; else ask). Just the
  **initial pin** — `/update` bumps it later, and nothing is synced from it here.
- **Does `$TARGET` actually define the profile?** Check, don't assume — a pointer naming
  a profile the template doesn't define writes cleanly, merges, and then kills the
  *first* `/rhiza:update` with "Profile 'X' was not found", one step removed from the
  mistake. This has happened twice (`rust-github-project`, which never existed;
  `rust-local`, which exists on `jebel-quant/rhiza`'s default branch and in no release).
  Reads one file from the template, so it costs a fraction of a sync:
```bash
uv run --python 3.12 --no-project python \
  "${RHIZA_ROOT}/plugin/scripts/check_template_profile.py" "$PROFILE" \
  --template-repo "$TEMPLATE_REPO" --ref "$TARGET" --template-host <github|gitlab>
```
  `$PROFILE` is what step 5 will write: `github-project`/`gitlab-project` for
  python/go, `rust-local` for rust. **Exit 0** — proceed. **Exit 1** — the profile is
  missing; **stop before writing the pointer** and give the user the choice its output
  supports: pin a ref that does define it (`--ref main` tracks the template's default
  branch, unreleased but working), pick one of the profiles it lists, or wait for a
  release. **Exit 2** — the template couldn't be read at all (network, unknown ref);
  that is not a wrong profile, so warn and continue, exactly as with the reachability
  check.

## 4. Work branch

`Read` `bundle/prompts/pr-base.md` and follow it with `BRANCH_PREFIX=rhiza_init`, passing
`OWNER`/`NAME`/visibility for the brand-new-repo path. It settles `$DEFAULT`, gets
`origin/$DEFAULT` to exist (asking *the user* to create the repo with an empty README
rather than ever pushing to the default branch), and leaves you on `$BRANCH`. If it
can't, it stops `/init` — don't work around that.

## 5. Write the pointer

`plugin/scripts/init_scaffold.py` writes `.rhiza/template.yml` and only that, only if absent:
```bash
uv run --python 3.12 --no-project python "${RHIZA_ROOT}/plugin/scripts/init_scaffold.py" . \
  --host <github|gitlab> --language <python|rust|go> \
  --template-repo "$TEMPLATE_REPO" --ref "$TARGET"
```

> **`--host` is about *this* repo, not the template.** It selects the profile, so a
> GitLab repo gets GitLab's CI. Where the *template* lives is a separate flag,
> `--template-host`, which defaults to GitHub — where the rhiza templates are. Only
> pass it when the template itself is GitLab-hosted. Conflating the two emitted
> `template-host: gitlab` for every GitLab repo, and the first sync then tried to clone
> `jebel-quant/rhiza` from gitlab.com and failed with "could not read Username".
Relay its `created`/`skipped` output, then commit it alone:
```bash
git add .rhiza/template.yml
git commit -m "chore: point repo at $TEMPLATE_REPO@$TARGET"
```

## 6. Skeleton, then license

- `Read` `bundle/prompts/skeleton.md` and follow it, telling it the language — it covers
  python, rust and go. **Not optional:** the template never ships a manifest, so without
  one `/update`'s gates fail outright — on python `make test` depends on `install` (a
  `uv sync`) and the synced `.rhiza/tests/test_pyproject.py` asserts a specific
  `[project]` shape; on rust every `cargo` target needs a `Cargo.toml`; on go every `go`
  target needs a `go.mod`. Carry `OWNER`/`NAME`/host in from step 2 so nothing is
  re-asked; let it ask for the description (and, on python, the Python version, or on
  go, the module path), which are its own to own.
- **Verify the manifest exists** — `test -f pyproject.toml` (python), `test -f
  Cargo.toml` (rust) or `test -f go.mod` (go). The procedure checks too, but check
  again: if it's missing, **stop and report**. Don't hand-write one, and don't commit or
  open a PR on a repo whose skeleton step failed.
- `Read` `bundle/prompts/license.md` and follow it. Skip only if the user wants the repo
  unlicensed.
- Commit what they produced:
  ```bash
  git add --all
  git commit -m "chore: add project skeleton + license metadata"
  ```
  A clean `git status` here just means both found everything already in place — say so
  and move on; a pointer-only PR is fine.

## 7. Push and open the PR

```bash
git push -u origin "$BRANCH"
uv run --python 3.12 --no-project python "${RHIZA_ROOT}/plugin/scripts/platform_cli.py" \
  pr-create --base "$DEFAULT" --head "$BRANCH" \
  --title "chore: make repo rhiza-managed" --body-file <BODY>
```
It detects the platform from `origin` and issues the right call — `gh pr create` or
`glab mr create`, which differ in subcommand, flag names *and* whether a body can come
from a file at all. Don't hand-write either: that mapping lived in prose once, and
`/update` shipped calling `gh` on GitLab repos. Add `--dry-run` to see the command
without creating anything.

Keep the body short: template repo + pinned ref + profile, what the PR contains, and
that **after merging the user runs `/update`** to pull the template content. If the
CLI is missing or unauthenticated, don't fail — the branch is pushed; print it and the
compare URL.

## 8. Report

The repo slug and URL, platform + profile, language, template repo + pinned ref, the
branch, and the **PR URL** (or compare URL). Then one line each for what the
procedures did: the pointer file, what `/skeleton` created or filled in plus the
Python version applied, and which license was written. State what is **not** in this
PR: no CI, no `Makefile`, no docs, no gates run.

Next steps: **review + merge**, then **run `/update`** (syncs the template, opens
PR #2); add your first module — the package is empty by design; `/docs` for
`README.md`/`CLAUDE.md`/`mkdocs.yml`; `/quality` anytime for a scorecard.
