Skip to content
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
10 changes: 10 additions & 0 deletions .rhiza/.env
Original file line number Diff line number Diff line change
@@ -1,3 +1,13 @@
# .rhiza/.env — project-local environment overrides (optional)
#
# This file is optional. If absent, rhiza.mk falls back to built-in defaults:
# SOURCE_FOLDER = src
# MARIMO_FOLDER = docs/notebooks
# RHIZA_CI_OS_MATRIX = ["ubuntu-latest"]
#
# Add or uncomment only the variables you want to override.
# Variables defined here are exported to all Make recipes.

MARIMO_FOLDER=docs/notebooks
SOURCE_FOLDER=src
RHIZA_CI_OS_MATRIX=["ubuntu-latest","macos-latest","windows-latest"]
2 changes: 1 addition & 1 deletion .rhiza/make.d/book.mk
Original file line number Diff line number Diff line change
Expand Up @@ -34,7 +34,7 @@ _book-notebooks:
name=$$(basename "$$nb" .py); \
printf "${BLUE}[INFO] Exporting $$nb -> ${ROOT}/docs/notebooks/$$name.html${RESET}\n"; \
abs_output="${ROOT}/docs/notebooks/$$name.html"; \
(cd "$$(dirname "$$nb")" && ${UV_BIN} run marimo export html --sandbox "$$(basename "$$nb")" -o "$$abs_output"); \
(cd "$$(dirname "$$nb")" && ${UV_BIN} run --with marimo marimo export html --sandbox "$$(basename "$$nb")" -o "$$abs_output"); \
done; \
else \
printf "${YELLOW}[WARN] MARIMO_FOLDER not set or missing, skipping notebook export${RESET}\n"; \
Expand Down
70 changes: 8 additions & 62 deletions .rhiza/make.d/bootstrap.mk
Original file line number Diff line number Diff line change
@@ -1,11 +1,13 @@
## .rhiza/make.d/bootstrap.mk - Bootstrap and Installation
# This file provides targets for setting up the development environment,
# installing dependencies, and cleaning project artifacts.
# This file provides the language-neutral half of setup: it puts uv/uvx on the
# path (rhiza runs pre-commit, mkdocs and semgrep through uvx whatever the
# project is written in), declares the install hooks, and cleans artifacts.
#
# `install` itself belongs to the language layer, because what it means differs:
# python.mk (bundle python-core) creates a virtualenv and runs `uv sync`.

# Declare phony targets (they don't produce files)
.PHONY: install-uv install clean pre-install post-install

UV_SYNC_ARGS ?= --all-extras --all-groups
.PHONY: install-uv clean pre-install post-install

# Hook targets (double-colon rules allow multiple definitions)
pre-install:: ; @:
Expand All @@ -29,62 +31,6 @@ install-uv: ## ensure uv/uvx is installed
fi; \
fi

install: pre-install install-uv ## install
# Create the virtual environment only if it doesn't exist
@if [ ! -d "${VENV}" ]; then \
${UV_BIN} venv $(if $(PYTHON_VERSION),--python $(PYTHON_VERSION)) ${VENV} || { printf "${RED}[ERROR] Failed to create virtual environment${RESET}\n"; exit 1; }; \
else \
printf "${BLUE}[INFO] Using existing virtual environment at ${VENV}, skipping creation${RESET}\n"; \
fi

# Install the dependencies from pyproject.toml (if it exists)
@if [ -f "pyproject.toml" ]; then \
if [ -f "uv.lock" ]; then \
if ! ${UV_BIN} lock --check >/dev/null 2>&1; then \
printf "${YELLOW}[WARN] uv.lock is out of sync with pyproject.toml${RESET}\n"; \
printf "${YELLOW} Run 'uv sync' to update your lock file and environment${RESET}\n"; \
printf "${YELLOW} Or run 'uv lock' to update only the lock file${RESET}\n"; \
exit 1; \
fi; \
printf "${BLUE}[INFO] Installing dependencies from lock file${RESET}\n"; \
${UV_BIN} sync $(UV_SYNC_ARGS) --frozen || { printf "${RED}[ERROR] Failed to install dependencies${RESET}\n"; exit 1; }; \
else \
printf "${YELLOW}[WARN] uv.lock not found. Generating lock file and installing dependencies...${RESET}\n"; \
${UV_BIN} sync $(UV_SYNC_ARGS) || { printf "${RED}[ERROR] Failed to install dependencies${RESET}\n"; exit 1; }; \
fi; \
else \
printf "${YELLOW}[WARN] No pyproject.toml found, skipping install${RESET}\n"; \
fi

# Install dev dependencies from .rhiza/requirements/*.txt files
@if [ -d ".rhiza/requirements" ] && ls .rhiza/requirements/*.txt >/dev/null 2>&1; then \
for req_file in .rhiza/requirements/*.txt; do \
if [ -f "$$req_file" ]; then \
printf "${BLUE}[INFO] Installing requirements from $$req_file${RESET}\n"; \
${UV_BIN} pip install -r "$$req_file" || { printf "${RED}[ERROR] Failed to install requirements from $$req_file${RESET}\n"; exit 1; }; \
fi; \
done; \
fi

# Check if there is requirements.txt file in the tests folder (legacy support)
@if [ -f "tests/requirements.txt" ]; then \
printf "${BLUE}[INFO] Installing requirements from tests/requirements.txt${RESET}\n"; \
${UV_BIN} pip install -r tests/requirements.txt || { printf "${RED}[ERROR] Failed to install test requirements${RESET}\n"; exit 1; }; \
fi

# Install pre-commit hooks
@if [ -f ".pre-commit-config.yaml" ]; then \
printf "${BLUE}[INFO] Installing pre-commit hooks...${RESET}\n"; \
${UVX_BIN} -p ${PYTHON_VERSION} pre-commit install || { printf "${YELLOW}[WARN] Failed to install pre-commit hooks${RESET}\n"; }; \
fi

@$(MAKE) post-install

# Display success message with activation instructions
@printf "\n${GREEN}[SUCCESS] Installation complete!${RESET}\n\n"
@printf "${BLUE}To activate the virtual environment, run:${RESET}\n"
@printf "${YELLOW} source ${VENV}/bin/activate${RESET}\n\n"

clean: ## Clean project artifacts and stale local branches
@printf "%bCleaning project...%b\n" "$(BLUE)" "$(RESET)"

Expand All @@ -106,4 +52,4 @@ clean: ## Clean project artifacts and stale local branches

@git fetch --prune

@git branch -vv | awk '/: gone]/{print $$1}' | xargs -r git branch -D
@git branch -vv | awk '/: gone]/ && $$1 != "*" && $$1 != "+" {print $$1}' | xargs -r git branch -D
42 changes: 42 additions & 0 deletions .rhiza/make.d/completions.mk
Original file line number Diff line number Diff line change
@@ -0,0 +1,42 @@
## .rhiza/make.d/completions.mk - Install shell tab-completion for make targets
# Copies the bundled completion scripts (.rhiza/completions/) into the user's
# completion directory so `make <TAB>` completes Rhiza targets. See
# .rhiza/completions/README.md for manual setup, aliases, and troubleshooting.

.PHONY: install-completions

# Which shell(s) to install completion for: bash, zsh, or both.
# make cannot reliably detect the login shell (it forces $SHELL to /bin/sh for
# recipes), so this defaults to `both` — installing an unused script is harmless.
# Narrow it with e.g. `make install-completions SHELL_KIND=zsh`.
SHELL_KIND ?= both

##@ Shell Completion
install-completions: ## install shell tab-completion for make targets (SHELL_KIND=bash|zsh|both)
@src=".rhiza/completions"; \
if [ ! -d "$$src" ]; then \
printf "${RED}[❌]${RESET} $$src not found — run from the project root.\n"; \
exit 1; \
fi; \
install_bash() { \
dest="$${XDG_DATA_HOME:-$$HOME/.local/share}/bash-completion/completions"; \
mkdir -p "$$dest"; \
cp "$$src/rhiza-completion.bash" "$$dest/make"; \
printf "${GREEN}[✓]${RESET} bash: installed to %s\n" "$$dest/make"; \
printf "${BLUE}[INFO]${RESET} start a new shell, or run: ${BOLD}source %s${RESET}\n" "$$dest/make"; \
}; \
install_zsh() { \
dest="$${XDG_DATA_HOME:-$$HOME/.local/share}/zsh/site-functions"; \
mkdir -p "$$dest"; \
cp "$$src/rhiza-completion.zsh" "$$dest/_make"; \
printf "${GREEN}[✓]${RESET} zsh: installed to %s\n" "$$dest/_make"; \
printf "${BLUE}[INFO]${RESET} if completion does not activate, add to ~/.zshrc:\n"; \
printf " ${BOLD}fpath=(%s \$$fpath); autoload -U compinit && compinit${RESET}\n" "$$dest"; \
}; \
case "$(SHELL_KIND)" in \
bash) install_bash ;; \
zsh) install_zsh ;; \
both) install_bash; install_zsh ;; \
*) printf "${YELLOW}[WARN]${RESET} unknown SHELL_KIND='%s'; installing both (use bash|zsh|both).\n" "$(SHELL_KIND)"; \
install_bash; install_zsh ;; \
esac
11 changes: 11 additions & 0 deletions .rhiza/make.d/custom-env.mk
Original file line number Diff line number Diff line change
Expand Up @@ -5,5 +5,16 @@
PROJECT_NAME_EXTRA := Rhiza Platform
LOG_LEVEL ?= INFO

# rhiza v1.3.3 changed the `typecheck` default from ty to `both`, which adds
# `mypy --strict`. That reports 43 errors in src/ today — missing generic
# parameters, Any returns, no pandas stubs — none of them introduced here, and
# all of them pre-dating this repo's move to v1.3.3. Pinning ty keeps the gate
# exactly as strict as it is on master rather than turning the upgrade into a
# 43-error source change.
#
# `make typecheck TYPECHECKER=mypy` still runs it on demand; drop this line once
# the source is strict-clean.
TYPECHECKER ?= ty

# Overriding core variables (be careful)
# VENV := .venv_custom
21 changes: 15 additions & 6 deletions .rhiza/make.d/doctor.mk
Original file line number Diff line number Diff line change
@@ -1,9 +1,18 @@
## .rhiza/make.d/doctor.mk - Developer prerequisite diagnostics
#
# Core checks only the tools it needs whatever the language: uv, make, git. A language
# layer appends its own diagnostics with a second `doctor::` rule — double-colon, like
# `pre-install::` and `test::`, so each rule runs in turn and a layer never has to know
# what core already checked. rust.mk uses this to catch a cargo that is not
# rustup-managed, which no version check would notice.
#
# Each rule runs in its own shell, so `failed` below is local to this one: a layer's rule
# reports and exits on its own account, and `make doctor` fails if any rule does.

.PHONY: doctor

##@ Dev
doctor: ## verify local prerequisites and print actionable guidance
doctor:: ## verify local prerequisites and print actionable guidance
@failed=0; \
version_ge() { \
awk -v a="$$1" -v b="$$2" 'BEGIN { \
Expand All @@ -21,33 +30,33 @@ doctor: ## verify local prerequisites and print actionable guidance
check_tool() { \
tool="$$1"; min="$$2"; install_url="$$3"; version_cmd="$$4"; gnu_required="$$5"; \
if ! command -v "$$tool" >/dev/null 2>&1; then \
printf "${RED}[❌]${RESET} %-9s missing — install: %s\n" "$$tool" "$$install_url"; \
printf "${RED}[FAIL]${RESET} %-11s missing - install: %s\n" "$$tool" "$$install_url"; \
failed=1; \
return; \
fi; \
version="$$(eval "$$version_cmd" 2>/dev/null)"; \
if [ -z "$$version" ]; then \
printf "${RED}[❌]${RESET} %-9s unknown version (required ≥ %s)\n" "$$tool" "$$min"; \
printf "${RED}[FAIL]${RESET} %-11s unknown version (required >= %s)\n" "$$tool" "$$min"; \
failed=1; \
return; \
fi; \
extra=""; \
if [ "$$gnu_required" = "gnu" ] && ! make --version 2>/dev/null | grep -q '^GNU Make'; then \
extra=" (GNU required)"; \
printf "${RED}[❌]${RESET} %-9s %-8s < %s%s\n" "$$tool" "$$version" "$$min" "$$extra"; \
printf "${RED}[FAIL]${RESET} %-11s %-8s < %s%s\n" "$$tool" "$$version" "$$min" "$$extra"; \
failed=1; \
return; \
fi; \
if version_ge "$$version" "$$min"; then \
if [ "$$gnu_required" = "gnu" ]; then \
extra=" (GNU required)"; \
fi; \
printf "${GREEN}[✅]${RESET} %-9s %-8s ≥ %s%s\n" "$$tool" "$$version" "$$min" "$$extra"; \
printf "${GREEN}[ OK ]${RESET} %-11s %-8s >= %s%s\n" "$$tool" "$$version" "$$min" "$$extra"; \
else \
if [ "$$gnu_required" = "gnu" ]; then \
extra=" (GNU required)"; \
fi; \
printf "${RED}[❌]${RESET} %-9s %-8s < %s%s\n" "$$tool" "$$version" "$$min" "$$extra"; \
printf "${RED}[FAIL]${RESET} %-11s %-8s < %s%s\n" "$$tool" "$$version" "$$min" "$$extra"; \
failed=1; \
fi; \
}; \
Expand Down
70 changes: 70 additions & 0 deletions .rhiza/make.d/github.mk
Original file line number Diff line number Diff line change
@@ -0,0 +1,70 @@
## github.mk - github repo maintenance and helpers
# This file is included by the main Makefile

# ── Forge Detection ──────────────────────────────────────────────────────────
# FORGE_TYPE is set once and reused by any target that needs to know the forge.
# Priority: .github/workflows/ → .gitlab-ci.yml / .gitlab/ → unknown
FORGE_TYPE := $(if $(wildcard .github/workflows/),github,$(if $(or $(wildcard .gitlab-ci.yml),$(wildcard .gitlab/)),gitlab,unknown))

# Declare phony targets
.PHONY: gh-install require-gh view-prs view-issues failed-workflows workflow-status latest-release whoami print-logo

# ── Internal guard ───────────────────────────────────────────────────────────
# Require the gh CLI; hard-fail if missing so downstream targets can depend on it.
require-gh:
@if ! command -v gh >/dev/null 2>&1; then \
printf "${RED}[ERROR] gh cli not found. Install from: https://github.com/cli/cli?tab=readme-ov-file#installation${RESET}\n"; \
exit 1; \
fi

##@ GitHub Helpers
gh-install: ## check for gh cli existence and install extensions
@if ! command -v gh >/dev/null 2>&1; then \
printf "${YELLOW}[WARN] gh cli not found.${RESET}\n"; \
printf "${BLUE}[INFO] Please install it from: https://github.com/cli/cli?tab=readme-ov-file#installation${RESET}\n"; \
else \
printf "${GREEN}[INFO] gh cli is installed.${RESET}\n"; \
fi

view-prs: gh-install ## list open pull requests
@printf "${BLUE}[INFO] Open Pull Requests:${RESET}\n"
@gh pr list --json number,title,author,headRefName,updatedAt --template \
'{{tablerow (printf "NUM" | color "bold") (printf "TITLE" | color "bold") (printf "AUTHOR" | color "bold") (printf "BRANCH" | color "bold") (printf "UPDATED" | color "bold")}}{{range .}}{{tablerow (printf "#%v" .number | color "green") .title (.author.login | color "cyan") (.headRefName | color "yellow") (timeago .updatedAt | color "white")}}{{end}}'

view-issues: gh-install ## list open issues
@printf "${BLUE}[INFO] Open Issues:${RESET}\n"
@gh issue list --json number,title,author,labels,updatedAt --template \
'{{tablerow (printf "NUM" | color "bold") (printf "TITLE" | color "bold") (printf "AUTHOR" | color "bold") (printf "LABELS" | color "bold") (printf "UPDATED" | color "bold")}}{{range .}}{{tablerow (printf "#%v" .number | color "green") .title (.author.login | color "cyan") (pluck "name" .labels | join ", " | color "yellow") (timeago .updatedAt | color "white")}}{{end}}'

failed-workflows: gh-install ## list recent failing workflow runs
@printf "${BLUE}[INFO] Recent Failing Workflow Runs:${RESET}\n"
@gh run list --limit 10 --status failure --json conclusion,name,headBranch,event,createdAt --template \
'{{tablerow (printf "STATUS" | color "bold") (printf "NAME" | color "bold") (printf "BRANCH" | color "bold") (printf "EVENT" | color "bold") (printf "TIME" | color "bold")}}{{range .}}{{tablerow (printf "%s" .conclusion | color "red") .name (.headBranch | color "cyan") (.event | color "yellow") (timeago .createdAt | color "white")}}{{end}}'

whoami: gh-install ## check github auth status
@printf "${BLUE}[INFO] GitHub Authentication Status:${RESET}\n"
@gh auth status --hostname github.com --json hosts --template \
'{{range $$host, $$accounts := .hosts}}{{range $$accounts}}{{if .active}} {{printf "✓" | color "green"}} Logged in to {{$$host}} account {{.login | color "bold"}} ({{.tokenSource}}){{"\n"}} Active account: {{printf "true" | color "green"}}{{"\n"}} Git operations protocol: {{.gitProtocol | color "yellow"}}{{"\n"}} Token scopes: {{.scopes | color "yellow"}}{{"\n"}}{{end}}{{end}}{{end}}'

workflow-status: require-gh ## show recent runs for the release workflow
@printf "${BOLD}Release Workflow Status${RESET}\n"
@printf "${BLUE}━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━${RESET}\n"
@RELEASE_WF=$$(gh workflow list --json name,id --jq '.[] | select(.name | test("release";"i")) | .name' 2>/dev/null | head -1); \
if [ -n "$$RELEASE_WF" ]; then \
printf "${BLUE}[INFO] Workflow: ${GREEN}$$RELEASE_WF${RESET}\n\n"; \
gh run list --workflow "$$RELEASE_WF" --limit 5 \
--json status,conclusion,headBranch,event,createdAt,displayTitle,url \
--template '{{tablerow (printf "STATUS" | color "bold") (printf "CONCLUSION" | color "bold") (printf "TITLE" | color "bold") (printf "EVENT" | color "bold") (printf "TIME" | color "bold")}}{{range .}}{{tablerow (printf "%s" .status | color "cyan") (printf "%s" (or .conclusion "—") | color (or (and (eq .conclusion "success") "green") (and (eq .conclusion "failure") "red") "yellow")) .displayTitle (.event | color "yellow") (timeago .createdAt | color "white")}}{{end}}'; \
else \
printf "${YELLOW}[WARN] No release workflow found in this repository${RESET}\n"; \
fi

latest-release: require-gh ## show information about the latest GitHub release
@printf "${BOLD}Latest Release${RESET}\n"
@printf "${BLUE}━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━${RESET}\n"
@if gh release view --json tagName --jq '.tagName' >/dev/null 2>&1; then \
gh release view --json tagName,name,publishedAt,url,isDraft,isPrerelease,author \
--template ' Tag: {{.tagName | color "green"}}{{"\n"}} Name: {{.name}}{{"\n"}} Author: {{.author.login}}{{"\n"}} Published: {{timeago .publishedAt}}{{"\n"}} Status: {{if .isDraft}}{{printf "Draft" | color "yellow"}}{{else if .isPrerelease}}{{printf "Pre-release" | color "yellow"}}{{else}}{{printf "Published" | color "green"}}{{end}}{{"\n"}} URL: {{.url}}{{"\n"}}'; \
else \
printf "${YELLOW}[WARN] No releases found in this repository${RESET}\n"; \
fi
18 changes: 18 additions & 0 deletions .rhiza/make.d/marimo.mk
Original file line number Diff line number Diff line change
@@ -1,6 +1,24 @@
## Makefile.marimo - Marimo notebook targets
# This file is included by the main Makefile

# Contribute the marimo notebook folder to the shared deptry scan defined in
# quality.mk. DEP004 (misplaced development dependency) is ignored because
# notebooks legitimately import packages declared as development dependencies.
ifneq ($(wildcard $(MARIMO_FOLDER)),)
DEPTRY_FOLDERS += $(MARIMO_FOLDER)
DEPTRY_IGNORE += --ignore DEP004
endif

# Exempt docutils from the licence gate in python.mk, on the same accumulator principle:
# the bundle that brings a dependency in owns the exemption for it, rather than every
# consumer rediscovering it. marimo depends on docutils, which is offered under a choice
# of licences and reports all of them as one string -- "BSD License; GNU General Public
# License (GPL); Public Domain". pip-licenses matches --fail-on against that string with
# no notion of *or*, so now that the gate actually matches, `GPL` hits a package every
# consumer of this bundle takes under BSD. Unconditional, unlike the deptry lines above:
# the dependency arrives with marimo itself, not with a notebook folder.
LICENSE_IGNORE_PACKAGES += docutils

# Declare phony targets (they don't produce files)
.PHONY: marimo-validate marimo

Expand Down
Loading
Loading