init · git:20260824.1f273f7 · 2026-08-24 · sha256 0a11defa1dd0ce5b
init git:20260824.1f273f7B
Immutable. This exact content is served forever at /api/v1/blob/0a11defa1dd0ce5b.
---
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.
argument-hint: "[repo name] (optional; defaults to the current folder name)"
allowed-tools: Bash(git*), Bash(gh*), Bash(glab*), Bash(uv*), Bash(curl*), Bash(brew*), Bash(ls*), Bash(basename*), Bash(pwd*), Bash(date*), Read, Write, Edit, AskUserQuestion, Skill
---
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 | `plugin/prompts/install-uv.md` | 1 |
| work branch off an untouched default | `plugin/prompts/pr-base.md` | 4 |
| skeleton + the `pyproject.toml` shape the gates need | `plugin/prompts/skeleton.md` (applies `plugin/prompts/python-version.md`) | 6 |
| SPDX metadata + the `LICENSE` file | `plugin/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
(`${CLAUDE_PLUGIN_ROOT}/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` `plugin/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 "${CLAUDE_PLUGIN_ROOT}/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 \
"${CLAUDE_PLUGIN_ROOT}/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` `plugin/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 "${CLAUDE_PLUGIN_ROOT}/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` `plugin/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` `plugin/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 "${CLAUDE_PLUGIN_ROOT}/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.