diff --git a/.claude/commands/rhiza_book.md b/.claude/commands/rhiza_book.md new file mode 100644 index 0000000..af4fee6 --- /dev/null +++ b/.claude/commands/rhiza_book.md @@ -0,0 +1,53 @@ +--- +description: Build the documentation book into a local _book folder and open it in the default browser +allowed-tools: Bash, Read +--- + +Build the MkDocs/zensical documentation book locally and open it in the user's +standard web browser. Follow the command-execution policy: always prefer +`make `; never invoke `.venv/bin/...` directly. + +This command depends on the **book** bundle (it expects a root `mkdocs.yml` and a +`make book` target from `.rhiza/make.d/book.mk`). If `mkdocs.yml` is missing, +stop and tell the user the `book` bundle is not set up here rather than guessing. + +Do the following, in order: + +1. **Build the book.** Run `make book`. This regenerates the `_book/` output + folder via zensical (it also runs the `_book-reports` / `_book-notebooks` + prerequisites). Run it in the foreground so build errors are visible. If it + fails, show the relevant output, diagnose the root cause, and stop — do not + try to open a stale or partial book. + +2. **Confirm the output.** Verify `_book/index.html` exists after the build. If + it does not, report what `make book` produced instead and stop. + +3. **Serve it locally (background).** Static zensical sites need to be served + over HTTP — search and navigation break under `file://`. Start a local server + for the built folder **in the background** so it does not block the session: + + ``` + (cd _book && uv run python -m http.server 8000) + ``` + + Use port 8000 by default (this matches `make serve`). If 8000 is already in + use, pick the next free port (8001, 8002, …) and use that URL throughout. + Do **not** use `make serve` here — it rebuilds the book a second time and + blocks the terminal; the book is already built from step 1. + +4. **Open the default browser.** Open the served URL with the platform's + standard opener: + - macOS: `open http://localhost:8000` + - Linux: `xdg-open http://localhost:8000` + - Windows: `start http://localhost:8000` + + Detect the platform and use the right one. + +5. **Report.** Tell the user the book was built at `_book/`, the URL it is being + served at, and how to stop the background server (e.g. the background task / + PID, or "kill the `http.server` process on port 8000"). Note that `_book/` is + a generated, gitignored artifact. + +If `$ARGUMENTS` is non-empty, treat it as an override hint — e.g. a custom port +(`/rhiza_book 9000`) or a request to only build without serving/opening (`/rhiza_book +build-only`) — and adjust accordingly. diff --git a/.claude/commands/rhiza_quality.md b/.claude/commands/rhiza_quality.md new file mode 100644 index 0000000..a00888d --- /dev/null +++ b/.claude/commands/rhiza_quality.md @@ -0,0 +1,80 @@ +--- +description: Run the Rhiza code-quality gate and score the repo (lint, types, docs, deps, security, tests) +--- + +Assess the quality of this repo against Rhiza standards. Follow the +command-execution policy: always prefer `make `; never invoke +`.venv/bin/...` directly. Run the gates 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, …) +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 deptry` — unused/missing/misplaced dependency analysis +5. `make security` — pip-audit + bandit scans +6. `make validate` — validate project structure against the Rhiza template (`.rhiza/template.yml`) +7. `make test` — full test suite **with** its coverage gate (slowest, run last) + +Guidelines: + +- Run all gates even after an early failure, so the full picture is visible + rather than stopping at the first red. +- If something fails, show the relevant output, diagnose the root cause, and + propose (or apply, if clearly correct, low-risk, **and** the fix is in a + locally-owned file per the scoping rule below) a fix. +- 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. + +**Coverage expectation.** `make test` enforces a coverage gate +(`COVERAGE_FAIL_UNDER`, default 90%; many projects raise it to 100%). Treat +anything below the configured threshold on locally-owned `src/` as a gap to +flag, not an acceptable baseline. When scoring the test-coverage subcategory, +the configured threshold is the bar for a 10; report uncovered lines +(`file:line`) and the test that would close each. + +**`make validate`.** A failure means this repo has drifted from the Rhiza +template (a synced file edited locally, or a missing/extra file). That is +in-scope: fix it by re-syncing from Rhiza or by adjusting `.rhiza/template.yml`, +not by editing the synced artifact in place. + +Then report: + +- A pass/fail summary per step. +- Failures grouped by file, with the specific rule/error and line. +- A prioritized list of what to fix first (blocking errors before style nits). + +Then analyse the repo and give marks on a scale of 1 to 10 for all relevant +subcategories. Pick the subcategories that fit what you actually observe — e.g. +linting/style, type safety, test pass rate, test coverage & depth, code +structure & readability, documentation, dependency & security hygiene, CI/tooling +health. For each: the score, a one-line justification grounded in evidence from +the checks above (and a quick look at the code where needed), and what would +raise it. Close with an overall score and the single highest-leverage +improvement. + +**Scope the scorecard to locally-owned items — not what the mother repo (Rhiza) +owns.** This project syncs its dev infrastructure from `jebel-quant/rhiza`; see +`CLAUDE.md` for the authoritative split and the `files:` block of +`.rhiza/template.lock` for the machine-generated list of synced files. Score +only what this repo actually controls — `src/`, `tests/`, `pyproject.toml`, +`README.md`, project-specific docs, `.rhiza/template.yml`, and any +locally-hardened config. Do **not** let Rhiza-managed files (the +`.github/workflows/*`, `Makefile`, `.pre-commit-config.yaml`, `pytest.ini`, +`ruff.toml`, the typecheck/mutation/fuzzing targets, etc.) drive the marks — a +gap there is fixed upstream in Rhiza, not here. If a relevant signal is +Rhiza-owned, note it as "upstream/out-of-scope" rather than scoring it against +this repo. + +Then, from the scorecard above, identify **actionable issues to improve the +score** — one per subcategory scoring below 10 (skip any that are maxed). For +each, give: a concrete title, the subcategory and current→target score it moves, +the specific file(s)/lines or config to change, and a crisp acceptance criterion +("done when…"). Keep them in-scope (locally-owned, per the scoping rule above) — +flag anything Rhiza-owned as upstream rather than listing it as a local action. +Order them by leverage (biggest score gain for least effort first). This is a +list of recommendations only — do not create GitHub issues or change code unless +I explicitly ask. + +If everything passes, say so plainly — but still produce the 1–10 subcategory +marks. Do not fix anything unless I ask — this command only assesses. diff --git a/.claude/commands/rhiza_update.md b/.claude/commands/rhiza_update.md new file mode 100644 index 0000000..a457a18 --- /dev/null +++ b/.claude/commands/rhiza_update.md @@ -0,0 +1,152 @@ +--- +description: Update the pinned Rhiza version in .rhiza/template.yml, sync, resolve conflicts, and verify +--- + +Update this repo's Rhiza template to a newer release: bump the pin in +`.rhiza/template.yml`, run the sync, resolve every conflict, verify the quality +gates, and open a PR. Follow the command-execution policy: always prefer +`make `; never invoke `.venv/bin/...` directly. + +`$ARGUMENTS` may name a target version (e.g. `v0.19.1`). If empty, target the +**latest** release of the upstream template repo. + +## 1. Determine current and target versions + +- Read `.rhiza/template.yml`: the `repository:` field is the upstream template + repo (usually `jebel-quant/rhiza`) and `ref:` is the currently pinned version. +- Resolve the target version: + - If `$ARGUMENTS` names a version, use it (verify the tag exists: + `gh api repos//git/ref/tags/`). + - Otherwise get the latest release: + `gh release view --repo --json tagName,publishedAt`. +- If `ref:` already equals the target, report "already up to date" and stop. +- Briefly summarize what's between the two versions when it's cheap to do so + (`gh release view`/release notes), so the reviewer knows what's landing. + +### The Rhiza CLI pin (`.rhiza/.rhiza-version`) — ask before changing it + +`.rhiza/.rhiza-version` separately pins the **Rhiza CLI** — the `rhiza` package on +PyPI that `make sync` runs as `uvx "rhiza=="`. It is independent of the +template `ref:` above, and there is no `$ARGUMENTS` override for it: always target +the **latest** published version. + +- Read the current pin from `.rhiza/.rhiza-version`. +- Resolve the latest published version on PyPI: + `curl -s https://pypi.org/pypi/rhiza/json` and read `.info.version`. +- If the pin already equals the latest, there is nothing to do — leave it. +- If a newer version is available, **ask the user whether to update the CLI** to + that latest version (state current → latest). Only bump `.rhiza/.rhiza-version` + if they agree; if they decline, leave the file untouched and proceed with the + template sync alone. + +## 2. Bump the pin(s) and commit (the tree must be clean to sync) + +- `make sync` refuses to run on a dirty tree, so the bump lands first. +- Branch off the default branch (don't work on `main`/`master` directly): + `git checkout -b sync/rhiza-`. +- Edit `ref:` in `.rhiza/template.yml` to the target version. If the user agreed + to a CLI bump above, also set `.rhiza/.rhiza-version` to the latest PyPI version + in the same step, so `make sync` runs with the chosen CLI. +- Commit the pin change(s) (e.g. `Chore: bump rhiza template ref `, + noting the CLI bump in the message when one was made). + +## 3. Sync + +- Run `make sync` (it invokes `rhiza sync`). Expect it to either complete + cleanly or report conflicts. It writes the refreshed `.rhiza/template.lock`. +- If it completes with no conflicts, skip to step 5. + +## 4. Resolve every conflict + +The sync is a 3-way merge. Two kinds of leftovers can appear — handle both, and +finish with **zero** `*.rej` files and **zero** conflict markers +(`<<<<<<<` / `=======` / `>>>>>>>`) anywhere tracked +(`git grep -lE '^(<<<<<<<|=======|>>>>>>>)'`). + +**`*.rej` files (rejected hunks).** The 3-way merge often *already applied* a +hunk and still drops a duplicate `.rej`. For each, verify whether the change is +already present in the file (the added `+` lines exist; no conflict markers +remain). If it is, the `.rej` is spurious — delete it. If a hunk genuinely did +not apply, apply it by hand, then delete the `.rej`. + +**Conflict-marked files.** Resolve by the ownership rule (see `CLAUDE.md` and the +`files:` block of `.rhiza/template.lock` for the authoritative managed-file list): + +- **Rhiza-managed files** (the `.github/workflows/*`, `Makefile`, + `.pre-commit-config.yaml`, `pytest.ini`, the `.rhiza/` engine, etc.): take the + **incoming/upstream** side — these are owned by the template and should match + it (`git checkout --theirs -- ` then `git add`). +- **Locally-owned or locally-hardened files** (notably `ruff.toml`, plus + `pyproject.toml`, `README.md`, `src/`, your `tests/`): **merge by hand** — + keep the local intent (e.g. stricter lint rules) while folding in genuine + upstream additions, and make the result internally coherent (dedupe, drop + comments that now contradict the config). + +Validate every touched workflow/YAML still parses before moving on. + +## 5. Verify the gates and fix fallout + +A version bump can tighten the gates (new lint rules, `mypy --strict`, expanded +docs-coverage scope, etc.) and surface pre-existing issues. Run them and get +them green: + +1. `make fmt` — pre-commit + lint +2. `make typecheck` +3. `make docs-coverage` +4. `make deptry` +5. `make security` +6. `make test` + +**Scope your fixes.** Fix issues only in **locally-owned** files (`src/`, +`tests/`, `pyproject.toml`, locally-hardened config). If a gate fails because of +a **Rhiza-managed** file, that is an upstream problem: fix it in +`jebel-quant/rhiza` and bump again — do **not** edit the synced artifact in +place. Call out any such upstream-owned failure explicitly rather than papering +over it locally. + +### Configure the CI variables/secrets the synced workflows need (fuzzing & mutation) + +If this sync added or updated the fuzzing/mutation workflows +(`.github/workflows/rhiza_fuzzing.yml`, `.github/workflows/rhiza_mutation.yml`), +make sure the configuration they read is present. These are **GitHub Actions +repository variables and secrets** — *not* local env vars or files — set under +**Settings → Secrets and variables → Actions** (or via `gh`), and documented in +`.github/CONFIG.md`. Skip this step entirely if neither workflow is present. + +Inspect what is already set (presence only — secret values are never readable): + +- `gh variable list` +- `gh secret list` + +**Mutation** (`rhiza_mutation.yml`) reads: + +- `MUTATION_ENABLED` (variable) — must be `true` for the mutation gate/badge to run. +- `GH_PAT` (secret) — git auth for installing private dependencies. +- `UV_EXTRA_INDEX_URL` (secret) — extra package index URL (with credentials) for private deps. + +**Fuzzing** (`rhiza_fuzzing.yml`) needs no user configuration — it uses the +automatic `GITHUB_TOKEN`, so do not prompt for any fuzzing secret. + +For each of the above that is **missing**, **ask the user** whether to set it and +for its value; set only the ones they provide: + +- variables: `gh variable set MUTATION_ENABLED --body true` +- secrets: `gh secret set GH_PAT` (let `gh` read the value from stdin — never + echo a secret value into the transcript or a commit). + +Leave anything the user declines unset, and note it in the PR body so the reviewer +knows the corresponding workflow step will be skipped or fail until it is configured. + +## 6. Commit, push, open a PR + +- Commit the resolution and any in-scope fixes with clear messages (one logical + change per commit: the conflict resolution, then each gate fix). +- Push the branch and open a PR (`gh pr create`) titled for the bump, e.g. + `Chore: sync Rhiza template `. In the body, summarize how each + conflict was resolved, list any gate fallout you fixed, and flag anything that + needs an **upstream** fix in Rhiza. +- Report a concise per-gate PASS/FAIL summary. If the workflow files changed, + note that pushing them needs a token with the `workflow` scope. + +Do not merge the PR. Stop after it is open and summarize what landed and what +(if anything) is blocked on an upstream Rhiza change.