Skip to content
Back to skills

Rhiza Update

ASecurity

Sync this rhiza-managed repo to the latest (or a given) template release and open a PR containing only template-owned files. Runs no gates and files no issues — use /rhiza:quality for a score.

  • 4 stars
  • 0 votes
  • 0 copies
  • 1 view
  • Added September 24, 2026
ai-agentspythonrustgoshellbashgit

Works with

  • claude code
  • cli

Security analysis

A100/100

Scanned September 24, 2026

npx -y skills add Jebel-Quant/rhiza-claude --skill rhiza-update --agent claude-code

Installs into .claude/skills of the current project.

Are you the author of Rhiza Update?

Add the live security badge to your README. It updates with every re-scan.

Security grade badge for Rhiza Update
[![Security: A — Skills Directory](https://www.skillsdirectory.com/api/skills/jebel-quant-rhiza-update/badge)](https://www.skillsdirectory.com/skills/jebel-quant-rhiza-update)

More formats (shields.io, HTML) on the badges page. Keep it an A: scan every change in CI with Pro.

Download with Pro
SKILL.md
---
name: rhiza-update
description: Sync this rhiza-managed repo to the latest (or a given) template release and open a PR containing only template-owned files. Runs no gates and files no issues — use /rhiza:quality for a score.
---

> **Portable copy of `plugin/skills/update/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:** [version e.g. v1.2.0]  (optional; defaults to the template's latest release).
>   The text below writes `$ARGUMENTS` for what the user passed;
>   substitute it yourself if your client does not.
> - **Runs:** `git`, `gh`, `glab`, `uv`, `cat`, `grep`. Nothing here enforces that list, where the
>   plugin's frontmatter did — your client has to permit them.

You are running `/update` in the **current working directory's repo**. Goal: bump the
template `ref`, apply the sync, and open a PR with **nothing but template-owned
files** in it.

**Only files that come from the template repository may be touched.** The sync
records exactly which paths it materialized in `.rhiza/template.lock`'s `files` list,
and `plugin/scripts/stage_synced.py` (step 7) stages precisely that set — so the guarantee is
mechanical, not something this prose has to be trusted to honour. **Never
`git add --all`** and never fold in unrelated edits: no reformatting of the repo's own
source, no dependency changes, no test fixes. Anything outside the set stays in the
working tree and gets reported.

**No gates, no scorecard, no issues.** `/update` syncs; `/quality` scores. Don't run
`make test`/`make lint`/`make fmt` here — `make fmt` in particular would rewrite the
repo's own files and break the rule above. Point the user at `/rhiza:quality` in the
report instead.

Argument (optional): `$ARGUMENTS` — an explicit template version tag like `v1.2.0`.
If empty, use the template's latest release.

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

## 1. Preconditions
- **`uv` first.** `Read` **`${RHIZA_ROOT}/bundle/prompts/install-uv.md`** and follow
  it before anything else (in a source checkout, `bundle/prompts/install-uv.md`). It's a
  one-line no-op when `uv` is already present. The sync runs through
  `uv run --python 3.12`, so if `uv --version` still fails afterwards, **stop** —
  don't fall back to a system `python3`, which on macOS is 3.9 and crashes `sync.py`
  on `datetime.UTC`. (`prompts/*.md` are internal procedures, not slash commands —
  not in any directory Claude Code scans, so the user can't invoke them; reach them
  with `Read`.)
- `.rhiza/template.yml` must exist. If not, stop: "Not a rhiza-managed repo (no
  .rhiza/template.yml)" — and point at `/rhiza:init`, which establishes that file.
- The working tree must be clean (`git status --porcelain`). If dirty, stop and show
  the dirty files; the sync refuses a dirty tree anyway (exit 2).
- Record `ORIG_BRANCH` (`git branch --show-current`) — step 8 returns to it — the
  default branch `DEFAULT` (`gh repo view --json defaultBranchRef --jq
  .defaultBranchRef.name`, else `git remote show origin`, else `main`), and the
  **platform** from `git remote get-url origin` (`github.com` → GitHub/`gh`; a GitLab
  host → GitLab/`glab`).

## 2. Resolve the target ref
- Read `repository` and the current `ref` (or `template-branch`, whichever key is
  present) from `.rhiza/template.yml`. Hold the repo as `TEMPLATE_REPO` — **this, not
  a hardcoded `jebel-quant/rhiza`**, is what to query. A repo may follow a fork, and
  bumping it to another project's tag would point the sync at a ref that doesn't
  exist there.
- `TARGET`:
  - `$ARGUMENTS` if non-empty, verbatim (ensure it starts with `v`);
  - else the latest release of `$TEMPLATE_REPO`:
    `gh release list -R "$TEMPLATE_REPO" -L 1 --json tagName --jq '.[0].tagName'`,
    falling back to `git ls-remote --tags --sort=-v:refname https://<host>/$TEMPLATE_REPO`
    for a GitLab-hosted template. If neither resolves, ask the user for the tag.
- **Major bumps aren't automatic.** If `TARGET`'s major exceeds the current ref's
  (e.g. `v0.19.x` → `v1.x`), stop and ask the user to confirm.
- If `TARGET` equals the current ref, nothing will change in `template.yml`. Say so
  and ask whether to re-run the sync anyway (to re-apply template content); stop
  unless they confirm.

## 3. Branch off the up-to-date default
`/update` may be invoked from any branch, but the PR must contain only the template
bump — so create the branch **before** editing anything. `Read`
**`${RHIZA_ROOT}/bundle/prompts/pr-base.md`** and follow it with
`BRANCH_PREFIX=rhiza_$TARGET`; it fetches `origin/$DEFAULT`, never pushes to it, and
leaves you on `$BRANCH`. Since the prefix already carries the tag, checking out an
existing `$BRANCH` is fine — a prior run made it off the same base. The tree was
confirmed clean in step 1, so nothing carries across.

## 4. Bump the ref and commit
On `$BRANCH`, set `ref:` (or `template-branch:`, whichever key is present) to
`"$TARGET"` in `.rhiza/template.yml`. Leave `profiles:`, `templates:`, `exclude:`,
`language:` and the rest exactly as they are — switching a platform profile is a
deliberate, separate change, not something a version bump should do.
```bash
git add .rhiza/template.yml
git commit -m "chore: bump rhiza to $TARGET"
```

## 5. Sync
```bash
uv run --python 3.12 --no-project python "${RHIZA_ROOT}/plugin/scripts/sync.py" .
```
(**Keep the quotes.** In a source checkout `${RHIZA_ROOT}/plugin` is empty — fall
back to `plugin/scripts/sync.py`. `--no-project` stops `uv` resolving the target repo's env
for this stdlib-only script.)

**Capture the exit code before doing anything else:**
- **0** — clean; skip step 6.
- **1** — synced *with conflicts*; the lock is written and merged files are on disk.
  Expected, not fatal — go to step 6.
- **2** — real failure (dirty tree, invalid `template.yml`, git error). **Stop and
  report**; nothing was applied. Say that `$BRANCH` still holds the step-4 bump commit
  **unpushed**, so the user can retry or delete the branch — don't leave that implicit.

## 6. Resolve conflicts — take upstream
Only when step 5 exited 1. This step rewrites files the user did not author, so it is
a script, not your text editing — run it and read the exit code:
```bash
uv run --python 3.12 --no-project python \
    "${RHIZA_ROOT}/plugin/scripts/resolve_conflicts.py" .
```
It takes the **upstream (theirs)** side of every `<<<<<<< … ======= … >>>>>>>` block:
a rhiza-managed file is the template's to own, so local divergence in one is drift to
undo, not work to preserve.

- **0** — every marker resolved, nothing outstanding. Continue.
- **1** — a `*.rej` file is present. **The sync cannot create these** — `git apply
  --reject` was the only thing that ever did, and the merge no longer runs `git apply` at
  all — so one here came from an older sync or a hand-run `git apply`. The script never
  applies a reject, because re-deriving where a hunk belongs is exactly the guess that
  corrupts a file. Show it and ask the user; do not stage a half-resolved tree.
- **2** — a malformed conflict block; **nothing was written**. Stop and report the file.

Then `git add` the resolved files (step 7's script does this from the lock, so you only
need to intervene for a file the script listed as outstanding).

## 7. Stage template-owned files only, then open the PR
The "template files only" rule is enforced by a script, not by you — don't hand-pick
paths, and never `git add --all`:
```bash
uv run --python 3.12 --no-project python "${RHIZA_ROOT}/plugin/scripts/stage_synced.py" .
```
It reads the lock's `files` list, stages exactly that plus `template.yml`/the lock
(deletions included), and prints anything it deliberately left behind. Relay that
output. Exit **1** means no lock — the sync never ran; stop. Exit **2** is a git
failure; stop. Exit **3** means the lock names a path that resolves outside the
repository and **nothing was staged** — don't hand-stage around it; report the entries
it named and stop, because a lock that can point outside the repo is not one to act on.

- **Anything reported as `left`** stays in the working tree. Name those paths in the
  PR body and the final report so the user decides — that's the expected outcome if a
  synced tool rewrote a repo-owned file.
- If nothing was staged, there's nothing to sync beyond the ref bump — say "already
  up to date after the bump" and continue with just the step-4 commit.
- Otherwise `SKIP=check-managed-files git commit -m "chore: apply rhiza sync $TARGET"`.
  **Keep the `SKIP=`.** rhiza-hooks' `check-managed-files` refuses a commit that touches
  any path in the lock's `files:` list, and `stage_synced.py` stages *exactly* that list —
  so this is the one commit in the plugin that legitimately rewrites managed files
  wholesale, and without the bypass it fails 100% of the time in every consumer that has
  adopted the hook. `SKIP` is the runner's own mechanism (pre-commit and prek both honour
  it), so it needs no cooperation from the hook, and it is scoped to this single commit.
  The hook's `--allow PATH` is per-path and wrong for a wholesale restore.
- `git push --set-upstream origin "$BRANCH"` (this is also what pushes the step-4
  bump commit — until now the branch was local only).
- Open the PR/MR into `$DEFAULT`:
  ```bash
  uv run --python 3.12 --no-project python "${RHIZA_ROOT}/plugin/scripts/platform_cli.py" \
    pr-create --base "$DEFAULT" --head "$BRANCH" \
    --title "chore: update rhiza to $TARGET" --body-file <BODY>
  ```
  It detects the platform from `origin` and issues `gh pr create` or `glab mr create`,
  which differ in subcommand *and* flag names. **This step used to be GitHub-only** —
  it detected GitLab, offered `gitlab-project`, then called `gh`. Don't hand-write
  either form; use the `pr-update` action to amend an existing request rather than
  erroring.
  - Body: the template repo + old ref → `$TARGET`, the count of files the sync
    changed, whether conflicts were resolved (taking upstream), anything left
    unstaged in the working tree, and a line noting that **no gates were run — run
    `/rhiza:quality` for a scorecard**.
- Exit **1** from the opener means the CLI is missing or failed. Don't treat that as
  fatal — the branch is already pushed; relay its note and print the compare URL.

## 8. Report and return
Short: `$TEMPLATE_REPO`, old ref → `$TARGET`, the branch, how many template files
changed, conflicts resolved (if any), anything deliberately left unstaged, and the
PR/MR URL. Close with the reminder that `/update` ran no gates — `/rhiza:quality`
produces the scorecard, and `/rhiza:status` shows what's now synced.

Then restore the invocation branch: `git checkout "$ORIG_BRANCH"`. Skip when
`ORIG_BRANCH` is empty (detached HEAD) or equals `$BRANCH`, and just report where you
are. Don't force it if the checkout fails — say you've left the tree on `$BRANCH` and
why.

Attribution

Is this your skill, or is something wrong with this listing? Request removal or report an issue. Author removals are honored within 72 hours.

Comments

Loading comments…

Newsletter

Get the weekly list of safe new skills, and the ones we pulled.