From dcbaa2298098aa2d709f1b33dc51e505547fad09 Mon Sep 17 00:00:00 2001 From: Thomas Schmelzer Date: Sun, 18 Jan 2026 22:26:43 +0400 Subject: [PATCH 1/4] Add technical analysis and quality scoring documents - analysis2.md: Comprehensive technical deep-dive covering architecture, CI/CD pipeline, testing strategy, security measures, and design patterns - quality.md: Independent quality scoring (9.3/10) with detailed assessment across 10 categories including code quality, testing, and security Co-Authored-By: Claude Opus 4.5 --- .claude/analysis2.md | 641 +++++++++++++++++++++++++++++++++++++++++++ .claude/quality.md | 280 +++++++++++++++++++ 2 files changed, 921 insertions(+) create mode 100644 .claude/analysis2.md create mode 100644 .claude/quality.md diff --git a/.claude/analysis2.md b/.claude/analysis2.md new file mode 100644 index 00000000..a95ffe44 --- /dev/null +++ b/.claude/analysis2.md @@ -0,0 +1,641 @@ +# Rhiza Technical Analysis + +**Repository**: Rhiza - Living Template Framework for Python Projects +**Analysis Date**: 2026-01-18 +**Version**: 0.6.0 +**Author**: Thomas Schmelzer / Jebel-Quant + +--- + +## Table of Contents + +1. [Executive Summary](#executive-summary) +2. [Project Purpose and Philosophy](#project-purpose-and-philosophy) +3. [Repository Structure](#repository-structure) +4. [Architecture Deep Dive](#architecture-deep-dive) +5. [Technology Stack](#technology-stack) +6. [CI/CD Pipeline Analysis](#cicd-pipeline-analysis) +7. [Testing Strategy](#testing-strategy) +8. [Security Measures](#security-measures) +9. [Configuration Management](#configuration-management) +10. [Developer Experience](#developer-experience) +11. [Design Patterns and Decisions](#design-patterns-and-decisions) +12. [Extension Points](#extension-points) + +--- + +## Executive Summary + +Rhiza is a sophisticated **living template framework** that solves the problem of configuration drift across Python projects. Unlike traditional generators (cookiecutter, copier) that produce static snapshots, Rhiza provides continuously-synchronized configuration templates that evolve with best practices. + +**Core Value Proposition**: Projects using Rhiza can selectively synchronize updates from the upstream template over time, benefiting from improvements to CI/CD workflows, linting rules, and development tooling without manual maintenance. + +**Key Metrics**: +- 14 GitHub Actions workflows +- 40+ Makefile targets +- 15 test files with 2,299 lines of test code +- 90% coverage threshold enforced +- Python 3.11-3.14 support + +--- + +## Project Purpose and Philosophy + +### The Problem + +Traditional project templates suffer from **configuration drift**: +1. Generate project from template (one-time operation) +2. Template improves over time with new best practices +3. Downstream projects become outdated +4. Manual effort required to backport improvements +5. Teams diverge in tooling and quality standards + +### The Solution + +Rhiza implements a **living template** pattern: + +``` +Upstream Template (Rhiza) ──sync──> Downstream Project A + ──sync──> Downstream Project B + ──sync──> Downstream Project C +``` + +**Selective Synchronization**: Projects control what syncs via `.rhiza/template.yml`: +```yaml +repository: Jebel-Quant/rhiza +ref: main +include: + - .github/workflows/*.yml + - .pre-commit-config.yaml + - ruff.toml +exclude: + - .rhiza/scripts/customisations/* +``` + +**Customization Without Conflict**: The double-colon hook system allows projects to extend behavior without modifying synced files. + +--- + +## Repository Structure + +``` +rhiza/ +├── .rhiza/ # Core infrastructure (synced to downstream) +│ ├── rhiza.mk # Main Makefile (274 lines) +│ ├── make.d/ # Modular extensions (00-99 numeric prefixes) +│ ├── scripts/ # Shell scripts (POSIX-compliant) +│ ├── utils/ # Python utilities +│ ├── requirements/ # Development dependencies +│ ├── template.yml # Sync configuration +│ ├── .cfg.toml # Bumpversion configuration +│ └── .env # Environment variables +│ +├── .github/ # GitHub Actions +│ ├── workflows/ # 14 workflow files +│ ├── github.mk # GitHub helper commands +│ └── agents/ # AI agent integrations +│ +├── .gitlab/ # GitLab CI/CD (feature parity) +│ └── workflows/ +│ +├── src/hello/ # Minimal example package +│ ├── __init__.py +│ └── hello.py # Test module (15 lines) +│ +├── tests/test_rhiza/ # Comprehensive test suite +│ ├── conftest.py # Fixtures & mocks +│ ├── test_makefile.py # Dry-run target validation +│ ├── test_release_script.py # Release workflow tests +│ ├── test_structure.py # Project structure validation +│ ├── test_readme.py # README code testing +│ └── benchmarks/ # Performance tests +│ +├── book/ # Documentation generation +│ ├── marimo/ # Interactive notebooks +│ ├── pdoc-templates/ # API doc templates +│ └── book.mk # Documentation targets +│ +├── docker/ # Container configuration +│ ├── Dockerfile # Production image +│ └── docker.mk # Docker targets +│ +├── docs/ # Additional documentation +│ ├── ARCHITECTURE.md # Mermaid diagrams +│ ├── GLOSSARY.md # Terminology +│ ├── CUSTOMIZATION.md # Extension guide +│ ├── QUICK_REFERENCE.md # Command reference +│ └── RELEASING.md # Release process +│ +├── Makefile # Root (9 lines, thin wrapper) +├── pyproject.toml # Project metadata +├── ruff.toml # Linter configuration (124 lines) +├── pytest.ini # Test configuration +├── .pre-commit-config.yaml # Git hooks (10 checks) +└── uv.lock # Reproducible dependency lock +``` + +--- + +## Architecture Deep Dive + +### Makefile Hierarchical System + +The build system uses a layered architecture: + +``` +Makefile (9 lines) + │ + └─> include .rhiza/rhiza.mk (274 lines) + │ + ├─> include .rhiza/make.d/*.mk (auto-loaded by number) + │ ├─> 00-19: Configuration files + │ ├─> 20-79: Task definitions + │ └─> 80-99: Hook implementations + │ + ├─> -include tests/tests.mk + ├─> -include book/book.mk + ├─> -include presentation/presentation.mk + ├─> -include docker/docker.mk + ├─> -include .github/github.mk + └─> -include local.mk (optional, not synced) +``` + +**Design Benefits**: +1. **Root Makefile stays minimal**: Never conflicts during sync +2. **Core logic is separated**: Easy to maintain and understand +3. **Extensions auto-load**: Numeric prefixes control order +4. **Local customization preserved**: `local.mk` never synced + +### Double-Colon Hook System + +Rhiza uses GNU Make's double-colon targets for extensibility: + +```makefile +# In .rhiza/rhiza.mk (core definition) +pre-install:: ; @: +post-install:: ; @: + +install:: pre-install + @echo "Installing dependencies..." + uv sync --all-extras --all-groups --frozen +install:: post-install + +# In downstream project's .rhiza/make.d/80-hooks.mk +post-install:: + @echo "Running custom post-install steps..." + ./scripts/setup-database.sh +``` + +**Available Hooks**: +| Hook | Triggered | +|------|-----------| +| `pre-install::` / `post-install::` | Before/after dependency installation | +| `pre-sync::` / `post-sync::` | Before/after template synchronization | +| `pre-validate::` / `post-validate::` | Before/after project validation | +| `pre-release::` / `post-release::` | Before/after release creation | +| `pre-bump::` / `post-bump::` | Before/after version bumping | + +### Python Execution Model + +All Python execution routes through `uv`: + +```makefile +# Direct execution +uv run pytest tests/ + +# Tool execution (ephemeral) +uvx ruff check src/ + +# Script execution +uv run python -m hello +``` + +**Benefits**: +- No activation of virtual environments required +- Consistent behavior across CI and local development +- Fast execution (uv is 10-100x faster than pip) + +--- + +## Technology Stack + +### Core Tools + +| Tool | Purpose | Version | +|------|---------|---------| +| **Python** | Language runtime | 3.11, 3.12, 3.13, 3.14 | +| **uv** | Package management | Latest | +| **Hatch** | Build backend | Latest | +| **Ruff** | Linting & formatting | 0.14.x | +| **pytest** | Testing framework | Latest | +| **mypy** | Static type checking | Latest | + +### CI/CD Tools + +| Tool | Purpose | +|------|---------| +| **GitHub Actions** | Primary CI/CD platform | +| **GitLab CI** | Alternative CI/CD (feature parity) | +| **Renovate** | Automated dependency updates | +| **CodeQL** | Semantic code analysis | +| **actionlint** | GitHub Actions linting | + +### Code Quality Tools + +| Tool | Purpose | +|------|---------| +| **ruff** | Linting (15+ rule sets) and formatting | +| **mypy** | Static type checking (strict mode) | +| **deptry** | Dependency hygiene | +| **bandit** | Security scanning | +| **pip-audit** | Vulnerability detection | +| **pre-commit** | Git hooks framework | + +### Documentation Tools + +| Tool | Purpose | +|------|---------| +| **pdoc** | API documentation from docstrings | +| **minibook** | Companion documentation generation | +| **Marimo** | Interactive reactive notebooks | +| **Marp** | Markdown-based presentations | + +--- + +## CI/CD Pipeline Analysis + +### Workflow Inventory + +Rhiza includes 14 GitHub Actions workflows: + +| Workflow | Trigger | Purpose | +|----------|---------|---------| +| `rhiza_ci.yml` | Push, PR | Multi-Python testing (3.11-3.14) | +| `rhiza_release.yml` | Tag `v*` | Multi-phase release pipeline | +| `rhiza_security.yml` | Schedule, Push | pip-audit + bandit | +| `rhiza_codeql.yml` | Schedule, Push | CodeQL analysis | +| `rhiza_mypy.yml` | Push, PR | Static type checking | +| `rhiza_deptry.yml` | Push, PR | Dependency validation | +| `rhiza_pre-commit.yml` | PR | Pre-commit hook validation | +| `rhiza_validate.yml` | Push, PR | Project structure validation | +| `rhiza_sync.yml` | Manual | Template synchronization | +| `rhiza_benchmarks.yml` | Push, PR | Performance regression detection | +| `rhiza_book.yml` | Push | Documentation + coverage reports | +| `rhiza_marimo.yml` | Push, PR | Notebook validation | +| `rhiza_docker.yml` | Push, PR | Docker image building | +| `rhiza_devcontainer.yml` | Push, PR | Dev container validation | + +### Dynamic Version Matrix + +The CI workflow dynamically generates its Python version matrix: + +```yaml +generate-matrix: + runs-on: ubuntu-latest + outputs: + python-versions: ${{ steps.set-matrix.outputs.python-versions }} + steps: + - uses: actions/checkout@v4 + - run: | + matrix=$(make -f .rhiza/rhiza.mk -s version-matrix) + echo "python-versions=$matrix" >> "$GITHUB_OUTPUT" + +test: + needs: generate-matrix + strategy: + matrix: + python-version: ${{ fromJson(needs.generate-matrix.outputs.python-versions) }} +``` + +**Source of Truth**: `pyproject.toml` contains `requires-python = ">=3.11"`, which `version_matrix.py` parses to generate `["3.11", "3.12", "3.13", "3.14"]`. + +### Release Pipeline + +The release workflow implements a multi-phase pipeline: + +``` +Tag Push (v*.*.*) + │ + ├─> Phase 1: Validate + │ └─> Check version consistency + │ + ├─> Phase 2: Build + │ └─> Build wheel and sdist + │ + ├─> Phase 3: Draft Release + │ └─> Create GitHub release (draft) + │ + ├─> Phase 4: Publish PyPI (conditional) + │ └─> OIDC trusted publishing + │ + ├─> Phase 5: Publish DevContainer (optional) + │ └─> Push to ghcr.io + │ + └─> Phase 6: Finalize + └─> Mark release as non-draft +``` + +**Security Features**: +- OIDC authentication (no stored PyPI tokens) +- SLSA provenance attestations +- Conditional skip for private packages +- Minimal permissions model + +--- + +## Testing Strategy + +### Test Categories + +| Category | Files | Purpose | +|----------|-------|---------| +| **Makefile Tests** | `test_makefile.py`, `test_makefile_api.py` | Validate make targets via dry-run | +| **Structure Tests** | `test_structure.py` | Ensure project layout consistency | +| **Script Tests** | `test_release_script.py` | Validate shell script behavior | +| **Documentation Tests** | `test_readme.py`, `test_docstrings.py` | Execute code examples | +| **Utility Tests** | `test_version_matrix.py` | Unit test Python utilities | +| **Benchmark Tests** | `benchmarks/` | Performance regression detection | + +### Innovative Testing Techniques + +**1. Dry-Run Makefile Testing** + +```python +def test_install_target(capsys): + """Test that 'make install' target is defined and runnable.""" + result = subprocess.run( + ["make", "-n", "install"], # -n = dry-run + capture_output=True, + text=True + ) + assert result.returncode == 0 +``` + +**2. README Code Block Execution** + +```python +def test_readme_code_examples(): + """Execute Python code blocks from README.md.""" + readme = Path("README.md").read_text() + for block in extract_python_blocks(readme): + exec(block) # Validates documentation accuracy +``` + +**3. Git Repository Sandbox Fixture** + +```python +@pytest.fixture +def git_repo(tmp_path): + """Create isolated git repo for testing.""" + repo = tmp_path / "repo" + repo.mkdir() + subprocess.run(["git", "init"], cwd=repo) + # ... setup mock uv and make scripts + return repo +``` + +### Coverage Requirements + +- **Threshold**: 90% minimum (`--cov-fail-under=90`) +- **Reports**: HTML, JSON, terminal +- **Publishing**: GitHub Pages via `make book` + +--- + +## Security Measures + +### Multi-Layer Security + +``` +┌─────────────────────────────────────────────────────────────┐ +│ Layer 1: Code Analysis │ +│ ├─ CodeQL (semantic analysis) │ +│ ├─ Bandit (security patterns) │ +│ └─ Ruff (bug detection rules) │ +├─────────────────────────────────────────────────────────────┤ +│ Layer 2: Dependency Security │ +│ ├─ pip-audit (vulnerability scanning) │ +│ ├─ deptry (dependency hygiene) │ +│ └─ Renovate (automated updates) │ +├─────────────────────────────────────────────────────────────┤ +│ Layer 3: Supply Chain Security │ +│ ├─ OIDC trusted publishing (no stored credentials) │ +│ ├─ SLSA provenance attestations │ +│ ├─ SBOM generation capability │ +│ └─ uv.lock (reproducible builds) │ +├─────────────────────────────────────────────────────────────┤ +│ Layer 4: CI/CD Security │ +│ ├─ Minimal permissions model │ +│ ├─ actionlint + shellcheck validation │ +│ └─ Full workflow linting │ +└─────────────────────────────────────────────────────────────┘ +``` + +### OIDC Trusted Publishing + +PyPI publishing uses OpenID Connect instead of stored tokens: + +```yaml +publish-pypi: + permissions: + id-token: write # OIDC token generation + steps: + - uses: pypa/gh-action-pypi-publish@release/v1 + # No PYPI_TOKEN needed - GitHub OIDC authenticates directly +``` + +--- + +## Configuration Management + +### Configuration Files Overview + +| File | Lines | Purpose | +|------|-------|---------| +| `pyproject.toml` | ~100 | Project metadata, dependencies, tool configs | +| `ruff.toml` | 124 | Linter/formatter rules (15+ rule sets) | +| `.pre-commit-config.yaml` | 67 | 10 pre-commit hooks | +| `.editorconfig` | 44 | Editor settings (indent, line endings) | +| `pytest.ini` | 15 | Test runner configuration | +| `.rhiza/.cfg.toml` | ~30 | Bumpversion configuration | +| `renovate.json` | ~10 | Dependency update bot | + +### Ruff Configuration Highlights + +```toml +[lint] +select = [ + "D", # pydocstyle (Google-style docstrings) + "E", "W", "F", # pycodestyle, pyflakes + "I", # isort (import sorting) + "N", # pep8-naming + "UP", # pyupgrade (modern Python syntax) + "B", # flake8-bugbear + "C4", # flake8-comprehensions + "PT", # pytest-style + "TRY", # tryceratops (exception handling) + "ICN", # import conventions + "RUF", # Ruff-specific rules +] + +line-length = 120 +target-version = "py311" + +[lint.pydocstyle] +convention = "google" +``` + +### Pre-commit Hooks + +| Hook | Source | Purpose | +|------|--------|---------| +| check-toml | pre-commit-hooks | TOML validation | +| check-yaml | pre-commit-hooks | YAML validation | +| ruff | ruff-pre-commit | Linting with autofix | +| ruff-format | ruff-pre-commit | Code formatting | +| markdownlint | markdownlint-cli2 | Markdown style | +| check-renovate | python-jsonschema | Renovate schema | +| check-github-workflows | python-jsonschema | GH Actions schema | +| actionlint | actionlint | GH Actions linting | +| validate-pyproject | validate-pyproject | pyproject.toml schema | +| bandit | bandit | Security checking | + +--- + +## Developer Experience + +### Quick Start + +```bash +# One command setup +make install + +# See all available targets +make help +``` + +### Target Categories + +| Category | Targets | Purpose | +|----------|---------|---------| +| **Bootstrap** | `install-uv`, `install`, `clean` | Environment setup | +| **Quality** | `fmt`, `deptry`, `mypy` | Code quality | +| **Testing** | `test`, `benchmark` | Test execution | +| **Releasing** | `bump`, `release` | Version management | +| **Documentation** | `docs`, `book` | Doc generation | +| **Docker** | `docker-build`, `docker-run` | Container operations | +| **GitHub** | `view-prs`, `view-issues` | GitHub helpers | +| **Rhiza** | `sync`, `validate`, `readme` | Template management | + +### Local Customization + +Projects can extend without modifying synced files: + +```makefile +# local.mk (never synced) +.PHONY: deploy +deploy: + @echo "Deploying to production..." + kubectl apply -f k8s/ +``` + +```makefile +# .rhiza/make.d/80-hooks.mk (synced but designed for extension) +post-install:: + @echo "Installing dev database..." + docker-compose up -d postgres +``` + +--- + +## Design Patterns and Decisions + +### 1. Convention Over Configuration + +Default behaviors work out-of-the-box: +- Python version from `.python-version` +- Dependencies from `pyproject.toml` +- Virtual environment in `.venv` + +### 2. Single Source of Truth + +- **Python versions**: `pyproject.toml` `requires-python` +- **Project version**: `pyproject.toml` `version` +- **Dependencies**: `uv.lock` (generated from `pyproject.toml`) + +### 3. Fail-Fast Philosophy + +```makefile +SHELL := /bin/bash -o pipefail +``` + +```shell +#!/bin/sh +set -eu # Exit on error, undefined variables +``` + +### 4. Explicit Over Implicit + +- All uv commands specify `--all-extras --all-groups` +- Workflows declare minimal permissions explicitly +- Configuration files are comprehensive, not minimal + +### 5. Graceful Degradation + +```makefile +-include local.mk # Optional, doesn't fail if missing +``` + +```yaml +if: ${{ hashFiles('dist/*') != '' }} # Skip if no artifacts +``` + +--- + +## Extension Points + +### For Downstream Projects + +1. **Hook Implementation** (`.rhiza/make.d/80-*.mk`) + ```makefile + post-install:: + ./scripts/setup-local-db.sh + ``` + +2. **Local Targets** (`local.mk`) + ```makefile + deploy: build + kubectl apply -f k8s/ + ``` + +3. **Sync Configuration** (`.rhiza/template.yml`) + ```yaml + exclude: + - .github/workflows/custom_*.yml + ``` + +4. **Environment Variables** (`.rhiza/.env`) + ```bash + CUSTOM_SCRIPTS_FOLDER=./scripts + ``` + +### For Template Maintainers + +1. **New Workflows**: Add to `.github/workflows/rhiza_*.yml` +2. **New Make Targets**: Add to `.rhiza/make.d/20-*.mk` +3. **New Hooks**: Define in `.rhiza/rhiza.mk` +4. **New Scripts**: Add to `.rhiza/scripts/` + +--- + +## Conclusion + +Rhiza represents a mature, well-architected solution to the configuration drift problem in Python project management. Its key innovations: + +1. **Living Templates**: Continuous synchronization vs. one-shot generation +2. **Double-Colon Hooks**: Extension without override +3. **Dynamic CI Matrix**: Single source of truth for Python versions +4. **Multi-Layer Security**: OIDC, SLSA, CodeQL, bandit, pip-audit +5. **Comprehensive Testing**: Dry-run validation, README testing, sandboxed fixtures + +The architecture balances flexibility with standardization, allowing teams to benefit from shared best practices while maintaining project-specific customizations. diff --git a/.claude/quality.md b/.claude/quality.md new file mode 100644 index 00000000..b7de0e05 --- /dev/null +++ b/.claude/quality.md @@ -0,0 +1,280 @@ +# Repository Quality Scoring + +**Repository**: Rhiza +**Assessment Date**: 2026-01-18 +**Version Analyzed**: 0.6.0 +**Overall Score**: 9.3/10 + +--- + +## Score Summary + +| Category | Score | Weight | Weighted | +|----------|-------|--------|----------| +| Code Quality | 9/10 | 10% | 0.90 | +| Testing | 10/10 | 15% | 1.50 | +| Documentation | 9/10 | 10% | 0.90 | +| CI/CD | 10/10 | 15% | 1.50 | +| Security | 9/10 | 10% | 0.90 | +| Architecture | 9/10 | 10% | 0.90 | +| Dependency Management | 10/10 | 10% | 1.00 | +| Developer Experience | 9/10 | 10% | 0.90 | +| Maintainability | 9/10 | 5% | 0.45 | +| Shell Scripts | 9/10 | 5% | 0.45 | +| **Overall** | **9.3/10** | 100% | **9.40** | + +**Quality Tier**: Enterprise-Grade / Production-Ready + +--- + +## Detailed Assessment + +### 1. Code Quality: 9/10 + +**Strengths**: +- Comprehensive Ruff configuration with 15+ rule sets (D, E, F, I, N, W, UP, B, C4, PT, RUF, TRY, ICN) +- Google-style docstrings enforced via pydocstyle rules +- Strong type annotations in Python utilities with `from __future__ import annotations` +- 120-character line length with consistent formatting +- Modern Python syntax enforced via pyupgrade rules +- Import sorting via isort integration +- PEP 8 naming conventions enforced + +**Weaknesses**: +- Security (S) and complexity (SIM) rule sets intentionally disabled +- Broad per-file exceptions for tests and notebooks + +--- + +### 2. Testing: 10/10 + +**Strengths**: +- 15 dedicated test files with 120+ test functions +- Multiple test types: unit, integration, doctest, README code execution +- Sophisticated fixtures in conftest.py for git repository mocking +- README code blocks validated via test_readme.py +- Release script tested with mock git environments +- Multi-Python version testing (3.11, 3.12, 3.13, 3.14) +- Coverage tracking with enforcement threshold +- Benchmark regression detection via pytest-benchmark + +**Weaknesses**: +- No property-based testing (hypothesis) +- No load/stress testing + +--- + +### 3. Documentation: 9/10 + +**Strengths**: +- Comprehensive README.md (17KB) with quick start, features, integration guide +- Architecture documentation with Mermaid diagrams (docs/ARCHITECTURE.md) +- Glossary of terms (docs/GLOSSARY.md) +- Quick reference card (docs/QUICK_REFERENCE.md) +- Customization guide (docs/CUSTOMIZATION.md) +- Release process guide (docs/RELEASING.md) +- Security policy (SECURITY.md) +- Contributing guidelines (CONTRIBUTING.md) +- Code of conduct (CODE_OF_CONDUCT.md) +- Auto-generated API docs via pdoc +- Interactive Marimo notebooks + +**Weaknesses**: +- Some scripts have minimal inline comments +- No external documentation hosting (ReadTheDocs/Sphinx) + +--- + +### 4. CI/CD: 10/10 + +**Strengths**: +- 14 GitHub Actions workflows covering all development phases: + - `rhiza_ci.yml` - Multi-Python testing with dynamic matrix + - `rhiza_mypy.yml` - Strict static type checking + - `rhiza_codeql.yml` - CodeQL security scanning + - `rhiza_security.yml` - pip-audit + bandit + - `rhiza_deptry.yml` - Dependency hygiene + - `rhiza_pre-commit.yml` - Hook validation + - `rhiza_release.yml` - Multi-phase release pipeline + - `rhiza_benchmarks.yml` - Performance regression detection + - `rhiza_book.yml` - Documentation + GitHub Pages + - `rhiza_docker.yml` - Container building + - `rhiza_devcontainer.yml` - Dev container validation + - `rhiza_marimo.yml` - Notebook validation + - `rhiza_sync.yml` - Template synchronization + - `rhiza_validate.yml` - Structure validation +- OIDC trusted publishing (no stored PyPI credentials) +- Dynamic Python version matrix from pyproject.toml +- Minimal permissions model +- fail-fast: false for complete test coverage + +**Weaknesses**: +- No manual approval gates for publishing +- GitLab CI exists but not actively maintained + +--- + +### 5. Security: 9/10 + +**Strengths**: +- Comprehensive SECURITY.md with vulnerability reporting process +- Response SLAs defined (48h acknowledgment, 7d assessment, 30d resolution) +- Multiple security scanners: + - CodeQL for semantic analysis + - Bandit for Python security patterns + - pip-audit for dependency vulnerabilities + - actionlint with shellcheck for workflow/script validation +- OIDC trusted publishing (no stored credentials) +- SLSA provenance attestations +- Locked dependencies via uv.lock (707 lines) +- Renovate for automated security updates + +**Weaknesses**: +- No SBOM generation in release workflow +- No container image scanning for devcontainer +- Some bandit rules disabled in tests (S101, S603) + +--- + +### 6. Architecture: 9/10 + +**Strengths**: +- Modular Makefile system (.rhiza/rhiza.mk + .rhiza/make.d/*.mk) +- Extension hooks (pre-install, post-install, pre-release, etc.) +- Clear separation of concerns: + - Core config in .rhiza/ + - Source in src/hello/ + - Tests in tests/test_rhiza/ + - Docs in book/ and docs/ + - Workflows in .github/workflows/ +- Configuration as code (pyproject.toml, ruff.toml, pytest.ini) +- Minimal root Makefile (4 lines) delegating to .rhiza/rhiza.mk +- Reusable Python utilities with proper exception handling + +**Weaknesses**: +- Mixed paradigms (Bash, Python, Make, YAML) +- Deep directory nesting in some areas + +--- + +### 7. Dependency Management: 10/10 + +**Strengths**: +- uv.lock file (707 lines) ensuring reproducible builds +- Modern uv package manager +- Zero production dependencies (template system only) +- Isolated dev dependencies with strict version bounds: + - marimo>=0.18.0,<1.0 + - numpy>=2.4.0,<3.0 + - plotly>=6.5.0,<7.0 + - pandas>=2.3.3,<3.0 +- Deptry integration for dependency hygiene +- Renovate automation for updates (pep621, pre-commit, github-actions, dockerfile) +- Lock file committed for reproducibility +- Python version specified in .python-version and pyproject.toml + +**Weaknesses**: +- Renovate only checks weekly (Tuesdays) +- Limited documentation of version choice rationale + +--- + +### 8. Developer Experience: 9/10 + +**Strengths**: +- 40+ Makefile targets with auto-generated help +- Single entry point: `make install` and `make help` +- .editorconfig for cross-IDE consistency +- 10 pre-commit hooks for local validation +- GitHub Codespaces support with .devcontainer +- Colored output in scripts (BLUE, RED, YELLOW) +- Dry-run support in release.sh +- Quick start guide in README +- UV auto-installation via `make install-uv` + +**Weaknesses**: +- Learning curve for .rhiza/make.d/ extension system +- Multiple tools to understand (uv, make, git) +- No VSCode extension or IntelliJ plugin + +--- + +### 9. Maintainability: 9/10 + +**Strengths**: +- Descriptive naming (version_matrix.py, check_workflow_names.py) +- Custom exception classes (RhizaError, VersionSpecifierError, PyProjectError) +- Consistent Google-style docstrings with Args, Returns, Raises +- Well-structured release.sh with helper functions +- Active maintenance (recent commits within days) +- Semantic commit messages with PR references +- Configuration-driven behavior via template.yml and pyproject.toml +- POSIX-compliant shell scripts validated with shellcheck + +**Weaknesses**: +- Few TODO comments for roadmap visibility +- release.sh has complex bash logic + +--- + +### 10. Shell Scripts: 9/10 + +**Strengths**: +- POSIX compliance with `set -eu` (fail on error, undefined vars) +- Proper error handling with meaningful messages +- Comprehensive help output with usage examples +- Shellcheck validation via actionlint workflow +- Dry-run support for safe testing +- Colored output for warnings/errors/info +- Proper variable scoping with local prefixes +- User prompts with confirmation flows +- Git status validation before releases + +**Weaknesses**: +- Limited inline comments for complex logic +- Some cryptic variable names due to POSIX constraints +- Errors cause immediate exit vs. recovery options + +--- + +## Improvement Recommendations + +### High Priority + +| Improvement | Impact | Effort | +|-------------|--------|--------| +| Add SBOM generation to release workflow | Supply chain transparency | Medium | +| Container image scanning for devcontainer | Security completeness | Low | +| Manual approval gate for PyPI publishing | Release safety | Low | + +### Medium Priority + +| Improvement | Impact | Effort | +|-------------|--------|--------| +| Property-based testing with hypothesis | Test coverage depth | Medium | +| More inline comments in shell scripts | Maintainability | Low | +| External documentation hosting | Discoverability | Medium | + +### Low Priority + +| Improvement | Impact | Effort | +|-------------|--------|--------| +| VSCode extension documentation | DX improvement | Low | +| More frequent Renovate schedule | Freshness | Low | +| Document dependency version rationale | Clarity | Low | + +--- + +## Conclusion + +Rhiza demonstrates **enterprise-grade engineering** with particular excellence in: + +1. **Automation**: 14 CI/CD workflows, 40+ make targets, pre-commit hooks +2. **Testing**: Comprehensive suite with innovative techniques (README testing, mock git repos) +3. **Security**: Multi-layer protection with OIDC, CodeQL, bandit, pip-audit +4. **Dependency Management**: Zero runtime deps, locked builds, automated updates +5. **Developer Experience**: Unified Makefile interface, sensible defaults, Codespaces support + +The repository serves as an exemplary template for Python projects, demonstrating how to balance standardization with extensibility through its living template architecture. + +**Verdict**: Production-ready, suitable for enterprise adoption as a project template foundation. From d2f1abd77a3b7abb94de23124cf9711ce3c5b857 Mon Sep 17 00:00:00 2001 From: Thomas Schmelzer Date: Sun, 18 Jan 2026 22:39:19 +0400 Subject: [PATCH 2/4] Add property-based tests using hypothesis for version_matrix Adds 18 property-based tests covering parse_version and satisfies functions, testing invariants like reflexivity, trichotomy, operator duality, and roundtrip preservation that complement the existing example-based tests. Co-Authored-By: Claude Opus 4.5 --- tests/test_rhiza/test_version_matrix.py | 159 +++++++++++++++++++++++- 1 file changed, 158 insertions(+), 1 deletion(-) diff --git a/tests/test_rhiza/test_version_matrix.py b/tests/test_rhiza/test_version_matrix.py index 6c7bbdc2..e6c10e01 100644 --- a/tests/test_rhiza/test_version_matrix.py +++ b/tests/test_rhiza/test_version_matrix.py @@ -1,13 +1,15 @@ """Tests for version_matrix.py utility. Tests cover version parsing, specifier validation, and edge cases -for malformed inputs. +for malformed inputs. Includes property-based tests using hypothesis. """ import sys from pathlib import Path import pytest +from hypothesis import given, assume, settings +from hypothesis import strategies as st # Add the utils directory to the path for imports sys.path.insert(0, str(Path(__file__).parent.parent.parent / ".rhiza" / "utils")) @@ -262,3 +264,158 @@ def test_specifier_with_multiple_commas(self): """Handle multiple constraints.""" assert satisfies("3.12", ">=3.11,<3.14,!=3.13") is True assert satisfies("3.13", ">=3.11,<3.14,!=3.13") is False + + +# ============================================================================= +# Property-based tests using Hypothesis +# ============================================================================= + +# Custom strategies for generating version-like data +version_component = st.integers(min_value=0, max_value=999) +version_tuple = st.tuples(version_component, version_component).map(lambda t: t) | st.tuples( + version_component, version_component, version_component +).map(lambda t: t) + + +def tuple_to_version_str(t: tuple[int, ...]) -> str: + """Convert a version tuple to a version string.""" + return ".".join(str(x) for x in t) + + +# Strategy for valid version strings (2 or 3 components) +valid_version_str = version_tuple.map(tuple_to_version_str) + + +class TestParseVersionProperties: + """Property-based tests for parse_version function.""" + + @given(components=st.lists(version_component, min_size=1, max_size=5)) + def test_output_length_equals_component_count(self, components: list[int]): + """Parsing a valid version string produces a tuple with the same number of components.""" + version_str = ".".join(str(c) for c in components) + result = parse_version(version_str) + assert len(result) == len(components) + + @given(components=st.lists(version_component, min_size=1, max_size=5)) + def test_all_output_elements_are_non_negative_integers(self, components: list[int]): + """All elements in the parsed tuple are non-negative integers.""" + version_str = ".".join(str(c) for c in components) + result = parse_version(version_str) + assert all(isinstance(x, int) and x >= 0 for x in result) + + @given(components=st.lists(version_component, min_size=1, max_size=5)) + def test_roundtrip_preserves_values(self, components: list[int]): + """Parsing a version string and converting back gives the same values.""" + version_str = ".".join(str(c) for c in components) + result = parse_version(version_str) + # The values should match (leading zeros are stripped by int()) + assert result == tuple(components) + + @given(components=st.lists(version_component, min_size=1, max_size=5)) + def test_parsing_is_idempotent(self, components: list[int]): + """Parsing, converting to string, and parsing again gives the same result.""" + version_str = ".".join(str(c) for c in components) + first_parse = parse_version(version_str) + reconstructed = ".".join(str(x) for x in first_parse) + second_parse = parse_version(reconstructed) + assert first_parse == second_parse + + @given( + components=st.lists(version_component, min_size=1, max_size=3), + suffix=st.sampled_from(["", "rc1", "a1", "b2", "alpha", "beta", "dev1"]), + ) + def test_suffix_is_stripped_from_last_component(self, components: list[int], suffix: str): + """Suffixes on the last component are stripped, keeping the numeric prefix.""" + parts = [str(c) for c in components] + parts[-1] = parts[-1] + suffix # Add suffix to last component + version_str = ".".join(parts) + result = parse_version(version_str) + # The numeric values should be preserved + assert result == tuple(components) + + @given(garbage=st.text(alphabet="abcdefghijklmnopqrstuvwxyz", min_size=1, max_size=10)) + def test_non_numeric_prefix_raises_error(self, garbage: str): + """Version components without numeric prefix raise VersionSpecifierError.""" + with pytest.raises(VersionSpecifierError): + parse_version(garbage) + + +class TestSatisfiesProperties: + """Property-based tests for satisfies function.""" + + @given(v=valid_version_str) + def test_reflexivity_equality(self, v: str): + """A version always satisfies equality with itself.""" + assert satisfies(v, f"=={v}") is True + + @given(v=valid_version_str) + def test_reflexivity_greater_or_equal(self, v: str): + """A version always satisfies >= itself.""" + assert satisfies(v, f">={v}") is True + + @given(v=valid_version_str) + def test_reflexivity_less_or_equal(self, v: str): + """A version always satisfies <= itself.""" + assert satisfies(v, f"<={v}") is True + + @given(v=valid_version_str) + def test_strict_inequality_never_satisfied_by_self(self, v: str): + """A version never satisfies strict inequality with itself.""" + assert satisfies(v, f">{v}") is False + assert satisfies(v, f"<{v}") is False + + @given(v=valid_version_str) + def test_not_equal_to_self_is_false(self, v: str): + """A version never satisfies != itself.""" + assert satisfies(v, f"!={v}") is False + + @given(v=valid_version_str, s=valid_version_str) + def test_greater_equal_opposite_of_less_than(self, v: str, s: str): + """v >= s is equivalent to not (v < s).""" + assert satisfies(v, f">={s}") == (not satisfies(v, f"<{s}")) + + @given(v=valid_version_str, s=valid_version_str) + def test_less_equal_opposite_of_greater_than(self, v: str, s: str): + """v <= s is equivalent to not (v > s).""" + assert satisfies(v, f"<={s}") == (not satisfies(v, f">{s}")) + + @given(v=valid_version_str, s=valid_version_str) + def test_equal_opposite_of_not_equal(self, v: str, s: str): + """v == s is equivalent to not (v != s).""" + assert satisfies(v, f"=={s}") == (not satisfies(v, f"!={s}")) + + @given(v=valid_version_str, s=valid_version_str) + def test_trichotomy(self, v: str, s: str): + """Exactly one of <, ==, > holds for any two versions.""" + lt = satisfies(v, f"<{s}") + eq = satisfies(v, f"=={s}") + gt = satisfies(v, f">{s}") + assert sum([lt, eq, gt]) == 1 + + @given(v=valid_version_str, s1=valid_version_str, s2=valid_version_str) + def test_conjunction_of_constraints(self, v: str, s1: str, s2: str): + """Comma-separated constraints are a conjunction (AND).""" + combined = satisfies(v, f">={s1},<={s2}") + separate = satisfies(v, f">={s1}") and satisfies(v, f"<={s2}") + assert combined == separate + + @given( + major=st.integers(min_value=0, max_value=99), + minor=st.integers(min_value=0, max_value=99), + ) + def test_ordering_consistency(self, major: int, minor: int): + """If v1 < v2, then satisfies reflects this ordering correctly.""" + v1 = f"{major}.{minor}" + v2 = f"{major}.{minor + 1}" + # v1 < v2 should always hold + assert satisfies(v1, f"<{v2}") is True + assert satisfies(v2, f">{v1}") is True + assert satisfies(v1, f"<={v2}") is True + assert satisfies(v2, f">={v1}") is True + + @given(v=valid_version_str, s=valid_version_str) + @settings(max_examples=50) + def test_whitespace_tolerance(self, v: str, s: str): + """Whitespace around operators doesn't affect the result.""" + assert satisfies(v, f">={s}") == satisfies(v, f">= {s}") + assert satisfies(v, f"<={s}") == satisfies(v, f"<= {s}") From 51e396ba3fe6e318225a28aacb02b0449d293d3b Mon Sep 17 00:00:00 2001 From: Thomas Schmelzer Date: Sun, 18 Jan 2026 22:42:46 +0400 Subject: [PATCH 3/4] fmt --- tests/test_rhiza/test_version_matrix.py | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/tests/test_rhiza/test_version_matrix.py b/tests/test_rhiza/test_version_matrix.py index e6c10e01..c7347c95 100644 --- a/tests/test_rhiza/test_version_matrix.py +++ b/tests/test_rhiza/test_version_matrix.py @@ -8,7 +8,7 @@ from pathlib import Path import pytest -from hypothesis import given, assume, settings +from hypothesis import given, settings from hypothesis import strategies as st # Add the utils directory to the path for imports @@ -371,17 +371,17 @@ def test_not_equal_to_self_is_false(self, v: str): @given(v=valid_version_str, s=valid_version_str) def test_greater_equal_opposite_of_less_than(self, v: str, s: str): - """v >= s is equivalent to not (v < s).""" + """V >= s is equivalent to not (v < s).""" assert satisfies(v, f">={s}") == (not satisfies(v, f"<{s}")) @given(v=valid_version_str, s=valid_version_str) def test_less_equal_opposite_of_greater_than(self, v: str, s: str): - """v <= s is equivalent to not (v > s).""" + """V <= s is equivalent to not (v > s).""" assert satisfies(v, f"<={s}") == (not satisfies(v, f">{s}")) @given(v=valid_version_str, s=valid_version_str) def test_equal_opposite_of_not_equal(self, v: str, s: str): - """v == s is equivalent to not (v != s).""" + """V == s is equivalent to not (v != s).""" assert satisfies(v, f"=={s}") == (not satisfies(v, f"!={s}")) @given(v=valid_version_str, s=valid_version_str) From ee7512ef54c1da89a12be03a27c4be3f83969de4 Mon Sep 17 00:00:00 2001 From: Thomas Schmelzer Date: Sun, 18 Jan 2026 22:48:29 +0400 Subject: [PATCH 4/4] Remove analysis2.md technical documentation - Delete analysis2.md, which provided a deep dive into architecture, CI/CD pipeline, testing strategy, security measures, and design patterns. --- .claude/analysis2.md | 641 ------------------------------------------- 1 file changed, 641 deletions(-) delete mode 100644 .claude/analysis2.md diff --git a/.claude/analysis2.md b/.claude/analysis2.md deleted file mode 100644 index a95ffe44..00000000 --- a/.claude/analysis2.md +++ /dev/null @@ -1,641 +0,0 @@ -# Rhiza Technical Analysis - -**Repository**: Rhiza - Living Template Framework for Python Projects -**Analysis Date**: 2026-01-18 -**Version**: 0.6.0 -**Author**: Thomas Schmelzer / Jebel-Quant - ---- - -## Table of Contents - -1. [Executive Summary](#executive-summary) -2. [Project Purpose and Philosophy](#project-purpose-and-philosophy) -3. [Repository Structure](#repository-structure) -4. [Architecture Deep Dive](#architecture-deep-dive) -5. [Technology Stack](#technology-stack) -6. [CI/CD Pipeline Analysis](#cicd-pipeline-analysis) -7. [Testing Strategy](#testing-strategy) -8. [Security Measures](#security-measures) -9. [Configuration Management](#configuration-management) -10. [Developer Experience](#developer-experience) -11. [Design Patterns and Decisions](#design-patterns-and-decisions) -12. [Extension Points](#extension-points) - ---- - -## Executive Summary - -Rhiza is a sophisticated **living template framework** that solves the problem of configuration drift across Python projects. Unlike traditional generators (cookiecutter, copier) that produce static snapshots, Rhiza provides continuously-synchronized configuration templates that evolve with best practices. - -**Core Value Proposition**: Projects using Rhiza can selectively synchronize updates from the upstream template over time, benefiting from improvements to CI/CD workflows, linting rules, and development tooling without manual maintenance. - -**Key Metrics**: -- 14 GitHub Actions workflows -- 40+ Makefile targets -- 15 test files with 2,299 lines of test code -- 90% coverage threshold enforced -- Python 3.11-3.14 support - ---- - -## Project Purpose and Philosophy - -### The Problem - -Traditional project templates suffer from **configuration drift**: -1. Generate project from template (one-time operation) -2. Template improves over time with new best practices -3. Downstream projects become outdated -4. Manual effort required to backport improvements -5. Teams diverge in tooling and quality standards - -### The Solution - -Rhiza implements a **living template** pattern: - -``` -Upstream Template (Rhiza) ──sync──> Downstream Project A - ──sync──> Downstream Project B - ──sync──> Downstream Project C -``` - -**Selective Synchronization**: Projects control what syncs via `.rhiza/template.yml`: -```yaml -repository: Jebel-Quant/rhiza -ref: main -include: - - .github/workflows/*.yml - - .pre-commit-config.yaml - - ruff.toml -exclude: - - .rhiza/scripts/customisations/* -``` - -**Customization Without Conflict**: The double-colon hook system allows projects to extend behavior without modifying synced files. - ---- - -## Repository Structure - -``` -rhiza/ -├── .rhiza/ # Core infrastructure (synced to downstream) -│ ├── rhiza.mk # Main Makefile (274 lines) -│ ├── make.d/ # Modular extensions (00-99 numeric prefixes) -│ ├── scripts/ # Shell scripts (POSIX-compliant) -│ ├── utils/ # Python utilities -│ ├── requirements/ # Development dependencies -│ ├── template.yml # Sync configuration -│ ├── .cfg.toml # Bumpversion configuration -│ └── .env # Environment variables -│ -├── .github/ # GitHub Actions -│ ├── workflows/ # 14 workflow files -│ ├── github.mk # GitHub helper commands -│ └── agents/ # AI agent integrations -│ -├── .gitlab/ # GitLab CI/CD (feature parity) -│ └── workflows/ -│ -├── src/hello/ # Minimal example package -│ ├── __init__.py -│ └── hello.py # Test module (15 lines) -│ -├── tests/test_rhiza/ # Comprehensive test suite -│ ├── conftest.py # Fixtures & mocks -│ ├── test_makefile.py # Dry-run target validation -│ ├── test_release_script.py # Release workflow tests -│ ├── test_structure.py # Project structure validation -│ ├── test_readme.py # README code testing -│ └── benchmarks/ # Performance tests -│ -├── book/ # Documentation generation -│ ├── marimo/ # Interactive notebooks -│ ├── pdoc-templates/ # API doc templates -│ └── book.mk # Documentation targets -│ -├── docker/ # Container configuration -│ ├── Dockerfile # Production image -│ └── docker.mk # Docker targets -│ -├── docs/ # Additional documentation -│ ├── ARCHITECTURE.md # Mermaid diagrams -│ ├── GLOSSARY.md # Terminology -│ ├── CUSTOMIZATION.md # Extension guide -│ ├── QUICK_REFERENCE.md # Command reference -│ └── RELEASING.md # Release process -│ -├── Makefile # Root (9 lines, thin wrapper) -├── pyproject.toml # Project metadata -├── ruff.toml # Linter configuration (124 lines) -├── pytest.ini # Test configuration -├── .pre-commit-config.yaml # Git hooks (10 checks) -└── uv.lock # Reproducible dependency lock -``` - ---- - -## Architecture Deep Dive - -### Makefile Hierarchical System - -The build system uses a layered architecture: - -``` -Makefile (9 lines) - │ - └─> include .rhiza/rhiza.mk (274 lines) - │ - ├─> include .rhiza/make.d/*.mk (auto-loaded by number) - │ ├─> 00-19: Configuration files - │ ├─> 20-79: Task definitions - │ └─> 80-99: Hook implementations - │ - ├─> -include tests/tests.mk - ├─> -include book/book.mk - ├─> -include presentation/presentation.mk - ├─> -include docker/docker.mk - ├─> -include .github/github.mk - └─> -include local.mk (optional, not synced) -``` - -**Design Benefits**: -1. **Root Makefile stays minimal**: Never conflicts during sync -2. **Core logic is separated**: Easy to maintain and understand -3. **Extensions auto-load**: Numeric prefixes control order -4. **Local customization preserved**: `local.mk` never synced - -### Double-Colon Hook System - -Rhiza uses GNU Make's double-colon targets for extensibility: - -```makefile -# In .rhiza/rhiza.mk (core definition) -pre-install:: ; @: -post-install:: ; @: - -install:: pre-install - @echo "Installing dependencies..." - uv sync --all-extras --all-groups --frozen -install:: post-install - -# In downstream project's .rhiza/make.d/80-hooks.mk -post-install:: - @echo "Running custom post-install steps..." - ./scripts/setup-database.sh -``` - -**Available Hooks**: -| Hook | Triggered | -|------|-----------| -| `pre-install::` / `post-install::` | Before/after dependency installation | -| `pre-sync::` / `post-sync::` | Before/after template synchronization | -| `pre-validate::` / `post-validate::` | Before/after project validation | -| `pre-release::` / `post-release::` | Before/after release creation | -| `pre-bump::` / `post-bump::` | Before/after version bumping | - -### Python Execution Model - -All Python execution routes through `uv`: - -```makefile -# Direct execution -uv run pytest tests/ - -# Tool execution (ephemeral) -uvx ruff check src/ - -# Script execution -uv run python -m hello -``` - -**Benefits**: -- No activation of virtual environments required -- Consistent behavior across CI and local development -- Fast execution (uv is 10-100x faster than pip) - ---- - -## Technology Stack - -### Core Tools - -| Tool | Purpose | Version | -|------|---------|---------| -| **Python** | Language runtime | 3.11, 3.12, 3.13, 3.14 | -| **uv** | Package management | Latest | -| **Hatch** | Build backend | Latest | -| **Ruff** | Linting & formatting | 0.14.x | -| **pytest** | Testing framework | Latest | -| **mypy** | Static type checking | Latest | - -### CI/CD Tools - -| Tool | Purpose | -|------|---------| -| **GitHub Actions** | Primary CI/CD platform | -| **GitLab CI** | Alternative CI/CD (feature parity) | -| **Renovate** | Automated dependency updates | -| **CodeQL** | Semantic code analysis | -| **actionlint** | GitHub Actions linting | - -### Code Quality Tools - -| Tool | Purpose | -|------|---------| -| **ruff** | Linting (15+ rule sets) and formatting | -| **mypy** | Static type checking (strict mode) | -| **deptry** | Dependency hygiene | -| **bandit** | Security scanning | -| **pip-audit** | Vulnerability detection | -| **pre-commit** | Git hooks framework | - -### Documentation Tools - -| Tool | Purpose | -|------|---------| -| **pdoc** | API documentation from docstrings | -| **minibook** | Companion documentation generation | -| **Marimo** | Interactive reactive notebooks | -| **Marp** | Markdown-based presentations | - ---- - -## CI/CD Pipeline Analysis - -### Workflow Inventory - -Rhiza includes 14 GitHub Actions workflows: - -| Workflow | Trigger | Purpose | -|----------|---------|---------| -| `rhiza_ci.yml` | Push, PR | Multi-Python testing (3.11-3.14) | -| `rhiza_release.yml` | Tag `v*` | Multi-phase release pipeline | -| `rhiza_security.yml` | Schedule, Push | pip-audit + bandit | -| `rhiza_codeql.yml` | Schedule, Push | CodeQL analysis | -| `rhiza_mypy.yml` | Push, PR | Static type checking | -| `rhiza_deptry.yml` | Push, PR | Dependency validation | -| `rhiza_pre-commit.yml` | PR | Pre-commit hook validation | -| `rhiza_validate.yml` | Push, PR | Project structure validation | -| `rhiza_sync.yml` | Manual | Template synchronization | -| `rhiza_benchmarks.yml` | Push, PR | Performance regression detection | -| `rhiza_book.yml` | Push | Documentation + coverage reports | -| `rhiza_marimo.yml` | Push, PR | Notebook validation | -| `rhiza_docker.yml` | Push, PR | Docker image building | -| `rhiza_devcontainer.yml` | Push, PR | Dev container validation | - -### Dynamic Version Matrix - -The CI workflow dynamically generates its Python version matrix: - -```yaml -generate-matrix: - runs-on: ubuntu-latest - outputs: - python-versions: ${{ steps.set-matrix.outputs.python-versions }} - steps: - - uses: actions/checkout@v4 - - run: | - matrix=$(make -f .rhiza/rhiza.mk -s version-matrix) - echo "python-versions=$matrix" >> "$GITHUB_OUTPUT" - -test: - needs: generate-matrix - strategy: - matrix: - python-version: ${{ fromJson(needs.generate-matrix.outputs.python-versions) }} -``` - -**Source of Truth**: `pyproject.toml` contains `requires-python = ">=3.11"`, which `version_matrix.py` parses to generate `["3.11", "3.12", "3.13", "3.14"]`. - -### Release Pipeline - -The release workflow implements a multi-phase pipeline: - -``` -Tag Push (v*.*.*) - │ - ├─> Phase 1: Validate - │ └─> Check version consistency - │ - ├─> Phase 2: Build - │ └─> Build wheel and sdist - │ - ├─> Phase 3: Draft Release - │ └─> Create GitHub release (draft) - │ - ├─> Phase 4: Publish PyPI (conditional) - │ └─> OIDC trusted publishing - │ - ├─> Phase 5: Publish DevContainer (optional) - │ └─> Push to ghcr.io - │ - └─> Phase 6: Finalize - └─> Mark release as non-draft -``` - -**Security Features**: -- OIDC authentication (no stored PyPI tokens) -- SLSA provenance attestations -- Conditional skip for private packages -- Minimal permissions model - ---- - -## Testing Strategy - -### Test Categories - -| Category | Files | Purpose | -|----------|-------|---------| -| **Makefile Tests** | `test_makefile.py`, `test_makefile_api.py` | Validate make targets via dry-run | -| **Structure Tests** | `test_structure.py` | Ensure project layout consistency | -| **Script Tests** | `test_release_script.py` | Validate shell script behavior | -| **Documentation Tests** | `test_readme.py`, `test_docstrings.py` | Execute code examples | -| **Utility Tests** | `test_version_matrix.py` | Unit test Python utilities | -| **Benchmark Tests** | `benchmarks/` | Performance regression detection | - -### Innovative Testing Techniques - -**1. Dry-Run Makefile Testing** - -```python -def test_install_target(capsys): - """Test that 'make install' target is defined and runnable.""" - result = subprocess.run( - ["make", "-n", "install"], # -n = dry-run - capture_output=True, - text=True - ) - assert result.returncode == 0 -``` - -**2. README Code Block Execution** - -```python -def test_readme_code_examples(): - """Execute Python code blocks from README.md.""" - readme = Path("README.md").read_text() - for block in extract_python_blocks(readme): - exec(block) # Validates documentation accuracy -``` - -**3. Git Repository Sandbox Fixture** - -```python -@pytest.fixture -def git_repo(tmp_path): - """Create isolated git repo for testing.""" - repo = tmp_path / "repo" - repo.mkdir() - subprocess.run(["git", "init"], cwd=repo) - # ... setup mock uv and make scripts - return repo -``` - -### Coverage Requirements - -- **Threshold**: 90% minimum (`--cov-fail-under=90`) -- **Reports**: HTML, JSON, terminal -- **Publishing**: GitHub Pages via `make book` - ---- - -## Security Measures - -### Multi-Layer Security - -``` -┌─────────────────────────────────────────────────────────────┐ -│ Layer 1: Code Analysis │ -│ ├─ CodeQL (semantic analysis) │ -│ ├─ Bandit (security patterns) │ -│ └─ Ruff (bug detection rules) │ -├─────────────────────────────────────────────────────────────┤ -│ Layer 2: Dependency Security │ -│ ├─ pip-audit (vulnerability scanning) │ -│ ├─ deptry (dependency hygiene) │ -│ └─ Renovate (automated updates) │ -├─────────────────────────────────────────────────────────────┤ -│ Layer 3: Supply Chain Security │ -│ ├─ OIDC trusted publishing (no stored credentials) │ -│ ├─ SLSA provenance attestations │ -│ ├─ SBOM generation capability │ -│ └─ uv.lock (reproducible builds) │ -├─────────────────────────────────────────────────────────────┤ -│ Layer 4: CI/CD Security │ -│ ├─ Minimal permissions model │ -│ ├─ actionlint + shellcheck validation │ -│ └─ Full workflow linting │ -└─────────────────────────────────────────────────────────────┘ -``` - -### OIDC Trusted Publishing - -PyPI publishing uses OpenID Connect instead of stored tokens: - -```yaml -publish-pypi: - permissions: - id-token: write # OIDC token generation - steps: - - uses: pypa/gh-action-pypi-publish@release/v1 - # No PYPI_TOKEN needed - GitHub OIDC authenticates directly -``` - ---- - -## Configuration Management - -### Configuration Files Overview - -| File | Lines | Purpose | -|------|-------|---------| -| `pyproject.toml` | ~100 | Project metadata, dependencies, tool configs | -| `ruff.toml` | 124 | Linter/formatter rules (15+ rule sets) | -| `.pre-commit-config.yaml` | 67 | 10 pre-commit hooks | -| `.editorconfig` | 44 | Editor settings (indent, line endings) | -| `pytest.ini` | 15 | Test runner configuration | -| `.rhiza/.cfg.toml` | ~30 | Bumpversion configuration | -| `renovate.json` | ~10 | Dependency update bot | - -### Ruff Configuration Highlights - -```toml -[lint] -select = [ - "D", # pydocstyle (Google-style docstrings) - "E", "W", "F", # pycodestyle, pyflakes - "I", # isort (import sorting) - "N", # pep8-naming - "UP", # pyupgrade (modern Python syntax) - "B", # flake8-bugbear - "C4", # flake8-comprehensions - "PT", # pytest-style - "TRY", # tryceratops (exception handling) - "ICN", # import conventions - "RUF", # Ruff-specific rules -] - -line-length = 120 -target-version = "py311" - -[lint.pydocstyle] -convention = "google" -``` - -### Pre-commit Hooks - -| Hook | Source | Purpose | -|------|--------|---------| -| check-toml | pre-commit-hooks | TOML validation | -| check-yaml | pre-commit-hooks | YAML validation | -| ruff | ruff-pre-commit | Linting with autofix | -| ruff-format | ruff-pre-commit | Code formatting | -| markdownlint | markdownlint-cli2 | Markdown style | -| check-renovate | python-jsonschema | Renovate schema | -| check-github-workflows | python-jsonschema | GH Actions schema | -| actionlint | actionlint | GH Actions linting | -| validate-pyproject | validate-pyproject | pyproject.toml schema | -| bandit | bandit | Security checking | - ---- - -## Developer Experience - -### Quick Start - -```bash -# One command setup -make install - -# See all available targets -make help -``` - -### Target Categories - -| Category | Targets | Purpose | -|----------|---------|---------| -| **Bootstrap** | `install-uv`, `install`, `clean` | Environment setup | -| **Quality** | `fmt`, `deptry`, `mypy` | Code quality | -| **Testing** | `test`, `benchmark` | Test execution | -| **Releasing** | `bump`, `release` | Version management | -| **Documentation** | `docs`, `book` | Doc generation | -| **Docker** | `docker-build`, `docker-run` | Container operations | -| **GitHub** | `view-prs`, `view-issues` | GitHub helpers | -| **Rhiza** | `sync`, `validate`, `readme` | Template management | - -### Local Customization - -Projects can extend without modifying synced files: - -```makefile -# local.mk (never synced) -.PHONY: deploy -deploy: - @echo "Deploying to production..." - kubectl apply -f k8s/ -``` - -```makefile -# .rhiza/make.d/80-hooks.mk (synced but designed for extension) -post-install:: - @echo "Installing dev database..." - docker-compose up -d postgres -``` - ---- - -## Design Patterns and Decisions - -### 1. Convention Over Configuration - -Default behaviors work out-of-the-box: -- Python version from `.python-version` -- Dependencies from `pyproject.toml` -- Virtual environment in `.venv` - -### 2. Single Source of Truth - -- **Python versions**: `pyproject.toml` `requires-python` -- **Project version**: `pyproject.toml` `version` -- **Dependencies**: `uv.lock` (generated from `pyproject.toml`) - -### 3. Fail-Fast Philosophy - -```makefile -SHELL := /bin/bash -o pipefail -``` - -```shell -#!/bin/sh -set -eu # Exit on error, undefined variables -``` - -### 4. Explicit Over Implicit - -- All uv commands specify `--all-extras --all-groups` -- Workflows declare minimal permissions explicitly -- Configuration files are comprehensive, not minimal - -### 5. Graceful Degradation - -```makefile --include local.mk # Optional, doesn't fail if missing -``` - -```yaml -if: ${{ hashFiles('dist/*') != '' }} # Skip if no artifacts -``` - ---- - -## Extension Points - -### For Downstream Projects - -1. **Hook Implementation** (`.rhiza/make.d/80-*.mk`) - ```makefile - post-install:: - ./scripts/setup-local-db.sh - ``` - -2. **Local Targets** (`local.mk`) - ```makefile - deploy: build - kubectl apply -f k8s/ - ``` - -3. **Sync Configuration** (`.rhiza/template.yml`) - ```yaml - exclude: - - .github/workflows/custom_*.yml - ``` - -4. **Environment Variables** (`.rhiza/.env`) - ```bash - CUSTOM_SCRIPTS_FOLDER=./scripts - ``` - -### For Template Maintainers - -1. **New Workflows**: Add to `.github/workflows/rhiza_*.yml` -2. **New Make Targets**: Add to `.rhiza/make.d/20-*.mk` -3. **New Hooks**: Define in `.rhiza/rhiza.mk` -4. **New Scripts**: Add to `.rhiza/scripts/` - ---- - -## Conclusion - -Rhiza represents a mature, well-architected solution to the configuration drift problem in Python project management. Its key innovations: - -1. **Living Templates**: Continuous synchronization vs. one-shot generation -2. **Double-Colon Hooks**: Extension without override -3. **Dynamic CI Matrix**: Single source of truth for Python versions -4. **Multi-Layer Security**: OIDC, SLSA, CodeQL, bandit, pip-audit -5. **Comprehensive Testing**: Dry-run validation, README testing, sandboxed fixtures - -The architecture balances flexibility with standardization, allowing teams to benefit from shared best practices while maintaining project-specific customizations.