Run the Rhiza code-quality gates and score this repo, then optionally file findings as issues. Falls back to a design-led assessment when the repo isn't rhiza-managed or synced. Assesses only — it proposes fixes but never applies them.
Scanned 9/5/2026
Install to Claude Code
npx -y skills add Jebel-Quant/rhiza-claude --skill quality --agent claude-codeInstalls into .claude/skills of the current project.
Are you the author of Quality?
Add the live security badge to your README — it updates automatically with every re-scan.
[](https://www.skillsdirectory.com/skills/jebel-quant-quality)More formats (shields.io, HTML) on the badges page.
---
description: Run the Rhiza code-quality gates and score this repo, then optionally file findings as issues. Falls back to a design-led assessment when the repo isn't rhiza-managed or synced. Assesses only — it proposes fixes but never applies them.
argument-hint: "[path or topic to scope the assessment to] (optional; defaults to the whole repo)"
allowed-tools: Bash(make*), Bash(git*), Bash(gh*), Bash(glab*), Bash(uv*), Bash(uvx*), Bash(python3*), Bash(grep*), Bash(find*), Bash(wc*), Bash(sed*), Bash(sort*), Bash(uniq*), Grep, Glob, Read, Edit, Write, AskUserQuestion
---
Assess the quality of the **current working directory's repo** against Rhiza
standards. This is the global variant of the per-repo `rhiza_quality` command
synced from `jebel-quant/rhiza`; it adapts to whichever repo it runs in by
reading that repo's `CLAUDE.md`, `.rhiza/template.lock`, and git remote at
runtime.
## 0. Establish the mode — how much of this repo is Rhiza's
These three checks come **first — before any `make`, any tool, any analysis**, because
they decide which half of this command applies:
```bash
test -f .rhiza/template.yml # rhiza-managed at all?
test -f .rhiza/template.lock # ...and actually synced?
test -f .rhiza/template-bundles.yml # ...or is this repo the template itself?
```
| `template.yml` + `template.lock` | **Full mode** — the template's gates plus the design assessment. |
| `template.yml` only | **Degraded mode** — managed but never synced, the state `/init` deliberately leaves behind. Mention `/rhiza:update` performs the first sync, then continue. |
| `template-bundles.yml`, no `template.yml` | **Template mode** — this repo *is* the template. See below; **never** suggest `/rhiza:init` or `/rhiza:update` here. |
| None of them | **Degraded mode** — not rhiza-managed. Mention `/rhiza:init` once, as information, then continue. |
**The pointer wins where both appear.** `template-bundles.yml` is decisive only when
there is no `template.yml`: a repo with a pointer is a *consumer*, whatever else its
`.rhiza/` holds.
**The second probe is the lock, not a synced file.** It used to be `.rhiza/rhiza.mk`,
which was a proxy: the sync delivered it, so its presence stood in for "a sync has
happened". Template v1.4 retired the make layer and stopped shipping it, and the proxy
inverted — every correctly and fully synced v1.4 repo answered "never synced" and was
quietly assessed in degraded mode, which is the *narrower* assessment and understates the
repo. `.rhiza/template.lock` is not a proxy: every sync writes it, at every template
version, and its `files:` block is the authoritative record of what was materialised.
`/rhiza:init` deliberately leaves it absent, which is exactly the never-synced state the
table's middle row describes. **Probe for an artefact the sync itself writes, never for
one a particular template version happened to ship.**
**Degraded mode is a narrower assessment, not a refusal.** Skip the template-delivered
gates, run whatever the repo's *own* makefile provides, and score the design work in
full. Say which mode you're in before the first gate, so nothing that follows is read
as a Rhiza verdict when it isn't.
**What degrading must never become is running the template's gates anyway.** Every
numbered gate below is a gate the sync provides — a `make` target through v1.3, a
`rhiza-task` task from v1.4 — and in an unsynced repo it exists in neither form, so
running it fails with "No rule to make target" and reporting that as FAIL describes a
broken repo when the truth is an unsynced one. That was the original reason this was a hard
stop, and it still holds — the answer is to *not run them and mark them unavailable*,
which is exactly what the existing out-of-scope rule already does for a reduced profile.
An unavailable gate is never a FAIL, in any mode.
So in degraded mode:
- **Skip** the template-delivered gates, and every `.rhiza/`-dependent step:
`make rhiza-test` (there is no `.rhiza/tests/`), template fidelity, and the
`known-issues.md` lookup (it is keyed by the template ref in `.rhiza/template.lock`,
which does not exist).
- **Except `fmt`, `typecheck`, `docs-coverage` and `deps`, which resolve in any repo.**
Each falls back to the repo's **own** tool config — see step 1 for the ladder and its
one hard limit: no config means out-of-scope, never the template's flags. These four are
gated in most mature repos, so skipping them reported gaps that usually are not there.
- **Run the bundled example checker, which needs no template at all** — gate 8. It is
stdlib-only Python this plugin ships, so it does not depend on a sync. Its README half
is language-neutral and runs on a Rust or Go repo too; only its docstring half is
Python's. It matters most here: the docstring and README checks a managed repo gets from
`make rhiza-test` have **no counterpart** in an unmanaged one, so without it nothing at
all asks whether this repo's documented examples still work.
- **Run** the targets `check_make_targets.py` reports as `undeclared` — the repo's own
documented ones. An unmanaged repo with a working `make test` and `make lint` is the
Go/Rust case generalised: the named list describes a template this repo isn't using,
and the discovered targets are the real gates. Score those.
- **Do steps 3 and 4 in full.** The design analysis reads source, not `.rhiza/`, so it
is unaffected — and in degraded mode it carries most of the assessment.
- **Score nothing you did not measure.** A skipped gate is out-of-scope, never a 0 and
never an assumed pass.
### Template mode — when this repo *is* the template
**The template repository has no pointer, no lock, and nothing above it.** Every
managed-repo probe comes back negative, so the table used to read it as "not
rhiza-managed" and offer `/rhiza:init` — to the repository that ships the bundles
`/init` would point at. What identifies it instead is `.rhiza/template-bundles.yml`, the
bundle-and-profile manifest every sync reads *out of* the template, alongside the
`bundles/<name>/` trees it indexes.
Mechanically this runs like degraded mode: the gates are the repo's own, and the design
analysis carries its usual weight. Four things differ, and each is a **correction** to
something degraded mode would otherwise get wrong:
- **Never offer `/rhiza:init` or `/rhiza:update`.** There is nothing above this repo to
adopt or to sync from. Degraded mode's once-only suggestion is not merely unhelpful
here, it is wrong — drop it rather than soften it.
- **Everything is in scope, for the opposite reason.** Degraded mode scores a repo's
infrastructure because there is no template; here it is scored because this repo *is*
the template. The workflows, the `Makefile`, `.pre-commit-config.yaml`, `ruff.toml` and
the bundle trees are its **product** — the most important thing to score, not the
least — so step 3 applies in full and carries more weight here than anywhere else.
Every consumer inherits whatever it finds.
- **"Upstream, out of scope" does not exist here.** In a managed repo a gap in a
Rhiza-owned file is fixed upstream and noted rather than scored. In this repo upstream
*is* here: every failure is in scope and fixable in this repository, and the
`known-issues.md` lookup is skipped only because it is keyed by a ref in a lock file
that does not exist — not because failures are somebody else's.
- **Template fidelity is not-applicable, never 0** — same as degraded mode, same reason:
there is nothing above to be faithful to.
**And the report says template mode, not degraded.** Degraded mode's required
boilerplate — "no Rhiza gate ran", "the number is not comparable to a full run" — is
false here. The gates that ran *are* the Rhiza gates; this is the repository they come
from.
### Which language is this repo?
**Ask before assuming.** The gate list below, `src/`, `pyproject.toml` and the
`src/` layout are all the **Python** profile. `/rhiza:init` supports Python, Go and
Rust, so a synced repo may legitimately be none of those things (**keep the quotes**;
in a source checkout use the repo-relative path):
```bash
uv run --python 3.12 --no-project python "${CLAUDE_PLUGIN_ROOT}/scripts/language_profile.py" --json
```
It returns the language and how it was determined, plus the **source root**, the
and the **manifest**. Feed those to `design-analysis.md` and
`scorecard.md` instead of the Python defaults. Exit **1** means the language could not
be determined — say so and score conservatively rather than assuming Python.
**Profiles vary, so probe before running:**
```bash
uv run --python 3.12 --no-project python "${CLAUDE_PLUGIN_ROOT}/scripts/check_make_targets.py"
```
It reads the gate list **out of this file's numbered list below** — so the probe can
never drift from what you're about to run — and reports each target as `available`,
`unavailable` or `undetermined`, using `make -n` so no recipe executes.
It also reports **`undeclared`**: targets the repo documents (`target: ## description`)
that the list below never named. On a Python repo those are mostly noise (`book`,
`clean`). On a Go or Rust repo they are the point — the named gates will nearly all be
unavailable, because the list describes a template that repo isn't using, and the
discovered targets are the real ones. **Run the relevant discovered targets and score
them**; reporting "no gates available" on a repo with a working `make test` is the
unsynced-repo error wearing a different hat.
Discovery rather than a per-language table is deliberate: this plugin has not seen the
Go and Rust templates' makefiles, and a table of targets it guessed at would be prose
asserting things about repos it has never run in.
**`undetermined` means the probe could not tell, and that is not availability.** A repo
whose `Makefile` is a shim — template v1.4 retired the make layer for a task runner,
leaving a `%:` rule that forwards anything it cannot resolve — answers `make -n` with
success for *every* target, typos included. So all the named gates come back
undetermined, and running them off the back of the probe is how a gate that does not
exist gets run and its "unknown task" error scored as a FAIL. In that repo:
- **Enumerate the real tasks first**, from the runner rather than from make. The probe
does it for you (**keep the quotes**; in a source checkout use the repo-relative path):
```bash
uv run --python 3.12 --no-project python "${CLAUDE_PLUGIN_ROOT}/scripts/check_make_targets.py" --tasks
```
One task name per line, read from the pin the makefile carries — and that pin matters:
a bare `uvx rhiza-task list` answers for whatever release is current, not necessarily
the one this repo's gates run under. Exit **1** means the question could not be asked
(no pin, no `uvx`, or the runner failed), which is *unmeasured* — never "this repo has
no gates". A bare `make help` shows the same catalogue plus any `local.mk` targets.
- **Match each named gate to a task before running it.** Score the task you actually
ran, under the concern the gate names.
- **A gate with no matching task was never provided** — out-of-scope, never FAIL, the
same as `unavailable`.
**Exit 1 means no makefile at all, and that is two different repos.** Read the note the
probe prints, because they need opposite advice:
- **No `.rhiza/template.lock` either** — unsynced, which step 0 already put in degraded
mode. Say so and move on.
- **The lock is present** — a **fully synced v1.4 repo that kept no shim `Makefile`.**
Having none is a legitimate state, not a broken sync: who owns the shim has moved
across v1.4 releases — v1.4.2 generated it per repo with `uvx rhiza-task shim`, later
ones sync it from the `core` bundle — and the lock, not a makefile, is what says a sync
happened. This repo is in **full mode**; its gates moved rather than went missing,
so they are reported `undetermined`, and telling it to run `/rhiza:update` is telling it
to redo what it has already done. Enumerate the real tasks and run each gate through the
runner instead:
```bash
uvx rhiza-task list
```
Then run each gate as a bare `uvx rhiza-task <task>`, matching it to a task first —
the names carried over from the make layer unchanged. A gate
with no matching task was never provided: out-of-scope, never FAIL. This is the same
discipline as the shim case above, minus the makefile — and the same rule holds about
what you must not do instead, which is supply your own thresholds.
`typecheck`, `security` and `docs-coverage` come from the template's *tests* bundle and
`deps`/`fmt` from *core*, so a reduced profile legitimately lacks some. **Run only the
available gates.** An unavailable one is scored **out-of-scope**, exactly like the
Rhiza-owned rule below — never FAIL.
## 1. Run the gates
Follow the command-execution policy: always prefer the repo's own front door —
`make <target>`, or `uvx rhiza-task <task>` in a v1.4 repo that kept no makefile — and
never invoke `.venv/bin/...` directly. Run them in order — cheapest checks first so fast
failures surface before the slow test suite — and collect results:
1. `make fmt` — pre-commit hooks + linting (ruff format/check, markdownlint, bandit, actionlint, …). **This one resolves in any repo — see below.**
2. `make typecheck` — static type checking (`ty`, and `mypy --strict` if configured) over `src/`
3. `make docs-coverage` — docstring coverage (interrogate) over `src/`
4. `make deps` — unused/missing/misplaced dependency analysis (the `deptry` tool). **The gate is `deps`, not `deptry`** — see below.
5. `make security` — pip-audit + bandit scans
6. `make rhiza-test` — run the template's own bundled tests under `.rhiza/tests/` (pyproject structure, docstrings, README)
7. `make test` — full test suite **with** its coverage gate (slowest, run last)
8. **Docstring examples and README fences** — run the bundled checker
(**keep the quotes**; in a source checkout use `plugin/scripts/check_doc_examples.py`):
```bash
uv run --python 3.12 --no-project python "${CLAUDE_PLUGIN_ROOT}/scripts/check_doc_examples.py" --source-root <SOURCE_ROOT> --json
```
`<SOURCE_ROOT>` is the one `language_profile.py` reported — never an assumed `src/`.
It answers two questions no other gate asks: where the **doctest examples** in the
docstrings are, and whether every **README fence** parses as the language it claims
(`bash -n` for shell, `compile()` for Python). Add **`--run`** to *execute* them —
`doctest` per module, and the `python` fences diffed against the ```result``` block
that follows them — which is the half that catches an example gone stale. Exit **1**
means an example is broken; exit **2** means there was nothing to check (no source
root, no README), which is out-of-scope in the usual way, never FAIL. Read the next
section before passing `--run`.
**Test-layout parity is deliberately not a gate, and must not be added back as one.**
`check_test_layout.py` still ships and a repo is free to enforce it locally, but
`/quality` neither runs it nor scores it. The rule it checks — one `test_<name>.py` per
module, one `Test<Class>` per class — describes how tests are *filed*, not whether they
are any good, and the two answers come apart in both directions: a suite can mirror every
module perfectly while asserting almost nothing, and a suite organised by behaviour can
cover more while failing the mirror outright. So the mark it produced was a proxy for
test quality that rewarded shape over substance, and the subcategories that measure the
thing itself — *test coverage & depth* and *test design quality* — were already carrying
it. It was also the assessment's most language-bound check, out-of-scope on every Go and
Rust repo, which made it a subcategory that could only ever lower a Python repo's score
relative to the others. A repo that does want the rule enforced should run the checker
from its own hook config, where a violation is a build failure rather than a deduction.
**The dependency gate is `deps`; `deptry` is a deprecated alias.** `deptry` names the
*tool*, which is not what the Rust and Go layers ever called the *target*, so the
template converged on `deps` for all three languages and kept `deptry` as a Python-only
alias that prints a deprecation warning and is scheduled for removal. v1.4's task runner
ships `deps` alone. This list named the alias until now — so `/quality` ran the one name
already gone on a current repo and on borrowed time everywhere else. A pre-v1.4 Python
repo answers to both; score it as `deps` either way.
**Why `make` and not the tools directly.** It's tempting to replace these with
`uvx ruff check`, `uvx interrogate`, `uvx bandit` and so on, which would let
`/quality` run anywhere. **Don't** — the two disagree, and measured against this very
repo they disagree in the worst direction: `interrogate` run bare reports FAILED at
99.5% where the configured hook passes, and `bandit` run bare reports a high-severity
finding where the configured hook passes. The arguments, thresholds and exclusions
live in the `make` target (and in `.pre-commit-config.yaml`), so a direct invocation
measures something else. For a command whose entire output is a score and a findings
list, that means inventing failures — and then filing issues for them. The `make`
target is the same entry point CI uses, which is exactly why its verdict is the one
worth scoring.
**The rule is about the thresholds, not about `make`.** Where v1.4 moved them into a
pinned `rhiza-task` release and the repo's `[tool.rhiza-task]` table, `uvx rhiza-task
<task>` is that same entry point and carries that same configuration; `uvx ruff check`
still is not.
### Why the examples are a gate of their own
`make docs-coverage` asks *"is there a docstring?"* and `markdownlint` asks *"is this
well-formed markdown?"*. Neither asks whether what the documentation **claims** is still
true, and that is the failure with the longest half-life in a repo: a docstring whose
`>>>` example returns something else now, or a README quickstart whose first command no
longer parses. Both keep rendering perfectly. The person who finds out is a newcomer, at
the worst possible moment, and a 10 on docstring coverage is exactly what makes it
invisible.
Three habits keep this gate honest:
- **`--run` is opt-in, and its cost is real.** Without it the examples are *parsed*:
malformed doctests and unparseable fences are caught with no import and no
dependencies. With it they are *executed* — which imports the repo's modules and runs
its README's Python, i.e. whatever module-level code they carry. That is the same trust
boundary `make test` crosses, so pass it once you have already decided to run the suite;
it is not a step to take on a repo you were asked only to read. Shell fences are
**never** executed either way — a README's shell is routinely `make clean`, `git push`,
`rm -rf`, and a fence that cannot parse is a documentation bug without running it.
- **Execute in the repo's own environment, or don't score that half.** Under
`--no-project` the interpreter has none of the project's dependencies, so most modules
fail to import. The checker reports those as **unimportable — unmeasured, not failing**,
and so must you: a missing dependency is a fact about how you invoked it, not a defect
in the docstring. When the repo has an environment (`uv run` inside a `uv` project,
`make test`'s own runner), run the `--run` pass there and say which one you used.
- **"0 examples" is a finding, not a pass.** A repo whose docstrings carry no examples
scores full marks on every gate above while documenting nothing executable. Report it
under user-facing documentation as the gap it is — and say plainly that the gate ran and
found nothing to check, rather than letting silence read as green.
**In full mode this is a second look, not a second score.** `make rhiza-test` already
runs the template's own `test_docstrings.py`, `test_readme.py` and
`test_readme_validation.py` — the three checks this checker reimplements, on the same
`+RHIZA_SKIP` convention. So in full mode take the verdict from `make rhiza-test`, run the
checker **without `--run`** for the inventory it adds (where the examples are, how many,
which fences carry no language at all), and don't count one result twice. In degraded mode
there is no `.rhiza/tests/`, nothing else checks any of this, and the checker *is* the
gate.
### Four gates resolve in any repo
**Read the rule above precisely: what is forbidden is running a tool with *thresholds
you supplied*.** It is not "only ever `make <target>`". Formatting, type checking,
docstring coverage and dependency hygiene are gated in most mature repos, so reporting
them all as unavailable usually understates coverage rather than describing a gap.
Each resolves the same way — **stop at the first hit** — and they keep the template's
order, so a fast failure still surfaces before the slow ones:
| Gate | Rung 1 | Rung 2: the repo's **own** config | Needs |
| --- | --- | --- | --- |
| `fmt` | `make fmt` | `.pre-commit-config.yaml` via `uvx prek run --all-files` (or `pre-commit`, if that is what CI names) | — |
| `typecheck` | `make typecheck` | `[tool.mypy]` in `pyproject.toml`, or `mypy.ini` / `setup.cfg` → `uvx mypy <source_root>` | a source root |
| `docs-coverage` | `make docs-coverage` | `[tool.interrogate]` in `pyproject.toml` → `uvx interrogate <source_root>` | a source root |
| `deps` | `make deps` | `[tool.deptry]`, or a dependency manifest to read → `uvx deptry <source_root>` | a source root **and a manifest** |
**Rung 2 is not the forbidden case**: every argument, threshold and exclusion still comes
from the repo's committed config, which is the whole thing the rule protects. The runner
is an entry point, not a judgement. Pass **no flags** — never `--strict`, never
`--fail-under`, never anything the repo did not ask for.
**And pass no path either, when the config already declares one.** A config may name its
own scope — `files = plugin/scripts` in `mypy.ini`, `files`/`packages` under
`[tool.mypy]`, `paths` under `[tool.interrogate]`. Appending the source root there does
not narrow the run, it **overrides** the repo's own scoping and measures a different
tree. This repo is the case: `mypy.ini` says `files = plugin/scripts`, so bare `uvx mypy`
checks the 45 modules CI checks, while `uvx mypy .` would sweep in `tests/` and report on
code the repo deliberately does not type-check. So: **read the config first.** Scope
declared → run the tool bare. No scope declared → pass `<source_root>` and say which one
you used.
**No config means rung 3: out-of-scope.** Do **not** fall back to the template's flags.
Copying `mypy --strict` or interrogate's threshold onto a repo that never chose them is
exactly how `interrogate` comes to report FAILED at 99.5% where a configured hook passes
— it measures a standard the repo never adopted, and then files issues for it. A repo
with no type-checking config has not failed type checking; it has declined to gate it,
which is a finding about *process*, reportable under step 3, not a FAIL here.
**`<source_root>` comes from `language_profile.py`, never from assuming `src/`.** On a
manifest-less repo the census reports `.`, which sweeps in `tests/` and anything else at
the root — so say which root was used, because `mypy .` and `mypy src` are different
measurements and only one of them is what CI would run.
**`deps` additionally needs a manifest, not just a source root.** It works by comparing
*declared* dependencies against *imported* ones, so with nothing declaring them it has no
left-hand side. `language_profile.py` reports `manifest_present` for exactly this kind of
question: false means rung 3, and the honest finding is "dependencies are not declared
anywhere", which is worth more than a tool error.
**`security` and `rhiza-test` stay template-only.** `rhiza-test` runs the template's own
bundled suite, which by definition is not there. `security` is pip-audit plus bandit, and
bandit is commonly a pre-commit hook already — so in most repos it is *already covered* by
`fmt` at rung 2, and running it again as its own gate would double-count one result.
Check whether the hook run included it before reporting a security gap.
**A discovered target is deliberately not a rung.** It is tempting to let a `lint`,
`format` or `check` target stand in, but the name does not tell you the scope: a repo's
`lint` routinely runs mypy, interrogate, the example checker and contract checkers
alongside ruff, so scoring it as `fmt` would credit formatting with most of the
toolchain. Matching on a target name is inference about what a target does, and that is
the same class of mistake as supplying your own thresholds — just wearing a `make`
prefix. Go to the config, which says exactly what runs.
**Say which rung answered**, as the narrower-base rule requires: "fmt via prek over
`.pre-commit-config.yaml`" is not the same evidence as `make fmt`.
**And score the underlying run once.** If a discovered target already ran those hooks,
`fmt` and that target rest on the same evidence — say so rather than reporting two
independent passes.
**None of this licenses rung 2 with a config you wrote**, or bare `uvx ruff check` when
a config exists. If there is no target and no config, the answer is rung 3.
Guidelines:
- Run each gate as a single, bare `make <target>` command — or a bare
`uvx rhiza-task <task>` where that is the front door — one Bash call per gate, no
pipe, redirect, chain or `cd` prefix. Read the output directly from the tool result
rather than capturing it to a file. **The plugin's `PreToolUse` hook enforces this**
(`plugin/hooks/hooks.json` → `plugin/scripts/hook_bash_guard.py`): a compound `make` is denied with
the reason, so re-run it bare. The hook is a backstop, not a substitute — it may be
absent in an older Claude Code, and the rule holds either way.
- Run all available gates even after an early failure, so the full picture is
visible rather than stopping at the first red.
- **If a gate fails, `Read`
`${CLAUDE_PLUGIN_ROOT}/prompts/known-issues.md` before diagnosing** (in a source
checkout, `plugin/prompts/known-issues.md`). Some failures are upstream and unsatisfiable
here, and it says which, keyed by the template ref in `.rhiza/template.lock`. A listed
one is scored **out-of-scope**, not FAIL — and one of them must specifically *not* be
"fixed", because the obvious fix makes the package unbuildable. A failure that isn't
listed is in scope; carry on.
- Then show the relevant output and diagnose the root cause.
**Propose the fix; don't apply it** — this command assesses, and a scoring run
that quietly edits code makes its own score unreproducible. The exception is
whatever `make fmt` auto-formats as part of running, which is unavoidable.
- If `$ARGUMENTS` is non-empty, scope the assessment to that path or topic
instead of the whole repo.
- End with a concise PASS/FAIL summary per gate.
**`make rhiza-test`.** Runs the test-suite the template syncs into `.rhiza/tests/` —
the `[project]` structure gate, docstring coverage, README validation. A failure there
is usually a *local* gap the template is checking for, so it is in scope; a failure in
the synced test files themselves is upstream — and `plugin/prompts/known-issues.md` names the
one that is unsatisfiable rather than merely upstream.
## 2. Report the gate results
Before any scoring, report what the gates said:
- a **PASS / FAIL / unavailable** line per gate — an undetermined gate you resolved to a
task is reported under the task you ran, and one you could not resolve is `unavailable`;
- failures grouped by file, with the specific rule or error and the line;
- a prioritized list of what to fix first — blocking errors before style nits.
**Whenever the base is narrower than a full Python run, say so.** Two situations
produce that, and they compound:
- **A Rust or Go repo.** The numbered gate list above is the **Python** profile — the
one this plugin has actually run against. On another language most of those targets
are unavailable, the marks come from the targets `check_make_targets.py`
*discovered*, and language-specific subcategories — gate 8's docstring half above
all, which is Python's even though its README half is not — are out-of-scope rather
than measured.
- **Degraded mode** (step 0). No template gate ran at all; every mark rests on the
repo's own discovered targets plus the design analysis.
So state, in the report:
- **which mode** produced it, and — in degraded mode — that no Rhiza gate ran;
- that the gates were **discovered**, and which ones ran;
- which subcategories were skipped as not applicable, and why (no template / not this
language);
- that the result rests on a narrower base and **is not comparable** to a full run.
A scorecard that silently rests on fewer gates reads as an equivalent number. Saying so
costs three lines and is the difference between a narrower score and a misleading one.
This matters most in degraded mode, where the number is *least* comparable and the
temptation to read it as "the Rhiza score" is strongest.
**Template mode is the one place none of that boilerplate belongs.** The base is not
narrower — the gates that ran are the ones every managed repo receives — so say which
mode produced the number and which gates ran, and stop. Copying "no Rhiza gate ran" onto
the repository the gates come from is the same class of wrong answer as telling it to
run `/rhiza:init`.
## 3. Degraded and template mode only — assess the infrastructure the template would have owned
**Skip this section entirely in full mode.** In a managed repo every file below is
Rhiza-owned, and scoring it would be the exact mistake the scoping rule exists to
prevent. In degraded mode there is no template, so all of it is the repo's own work —
and *nothing else is checking it*. This is where degraded mode stops being a reduced
assessment and starts being a different one.
**In template mode it is not a fallback at all — it is the main event.** These files are
what the repository ships; every consumer inherits them. Score them first and hardest,
and read "the repo's own work" below as "the product".
The gates in step 1 answer "is the code clean?". These answer **"does this repo's
quality survive contact with a second contributor?"** — which is what a template buys
you, and what its absence puts at risk.
- **Are the gates wired into CI, or only runnable locally?** Read `.github/workflows/`
(or `.gitlab-ci.yml`) and check that the targets you just ran are actually invoked
there. A `make test` that only ever runs on the author's machine is a gate in name
only: it constrains one person's habits, not the repo. **This is the single
highest-value check in degraded mode** — a managed repo gets CI wiring from the
template and cannot get this wrong, so an unmanaged one is where it silently goes
missing.
- **Is the toolchain reproducible?** A lockfile committed, a pinned language version,
and third-party CI actions pinned to a tag or SHA rather than a moving branch. Name
what is missing; an unpinned `@main` in someone else's action is a supply-chain
decision the repo made without recording it.
- **Is there a documented way in?** `make help`, or a README section that names the
commands. If the only way to learn how to run the gates is to read the `Makefile`,
say so.
- **Does a present config actually run?** A `.pre-commit-config.yaml` that no CI job
invokes is a file, not a gate — the same trap as the CI point above, one level down.
**Gather, don't assume.** Every claim names the file you read and the line that
supports it, exactly as `design-analysis.md` requires. "No CI" and "CI that doesn't run
the gates" are different findings with different fixes, and only reading the workflow
tells them apart.
**Score these as their own subcategories** — CI wiring, toolchain reproducibility,
contributor onboarding — and say plainly why they are in scope: *because the repo is
unmanaged*, or, in template mode, *because the repo is the template*. A reader comparing
this run to a managed repo's needs to know these marks have no counterpart there, rather
than assuming the managed repo scored 10 on them.
**And if the answer is "adopt the template", say it once.** `/rhiza:init` exists and
wiring CI by hand is work; noting that is useful. Repeating it per finding turns an
assessment into a sales pitch, and step 0 already said it once. **In template mode there
is no such answer to give** — this repository is the one being adopted, so the finding
stands on its own and the suggestion is simply omitted.
## 4. Gather the design evidence
`Read` **`${CLAUDE_PLUGIN_ROOT}/prompts/design-analysis.md`** and follow it (in a
source checkout, `plugin/prompts/design-analysis.md`). Complexity and architecture are the two
subcategories `/quality` must *always* score, and **no `make` gate measures either** —
so that evidence is gathered by hand, or the marks are guesses.
## 5. Score, and offer to file findings
`Read` **`${CLAUDE_PLUGIN_ROOT}/prompts/scorecard.md`** and follow it. It owns the
scoping rule, the subcategory list, the coverage bar, the findings format, and the
issue-filing menu. Feed it the step-2 gate results, the step-3 infrastructure findings
(degraded mode only) and the step-4 design evidence; it turns
them into marks, then findings, then — only with an explicit selection — issues.
Both files are **internal procedures, not slash commands** — deliberately kept out of
any directory Claude Code scans, so the user can't invoke them, and `Read` is how you
reach them. Don't
restate their rules here or score from memory: the scoping rule in particular is what
stops a managed repo being marked down for its own template.
> **`/quality` is the only command that scores.** `/update` used to invoke it and
> carry a scorecard in its PR; it no longer does — it syncs the template and nothing
> else, so that a template bump PR can't be polluted by `make fmt` rewriting the
> repo's own files. Run `/quality` yourself, whenever you want a score. Nothing
> invokes this command on your behalf, so there is no assessment-only mode to
> switch into.
Is this your skill, or is something wrong with this listing? Request removal or report an issue. Author removals are honored within 72 hours.
No comments yet. Be the first to comment!