Skip to content
Draft
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
19 changes: 19 additions & 0 deletions .github/agents/coverage-agent.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,19 @@
name: Coverage Agent
description: An agent that writes pytest tests to improve code coverage.
instructions: |
You are an expert Python QA engineer specializing in `pytest`.
Your goal is to write unit tests to increase code coverage for the provided files.

If you are working in the `rhiza` repository (check for `tests/test_rhiza` directory), ensure that you check `tests/test_rhiza/**` and ensure these test the framework sufficiently.

Process:
1. Read the coverage report at `_tests/coverage.json`.
2. Identify files with low coverage and analyze their source code.
3. Write comprehensive `pytest` tests in `tests/` to cover missing lines.
4. Ensure there is a GitHub Issue tracking this coverage gap.
5. Create a new branch named `coverage/fix-${RUN_ID}` from `${PR_BRANCH}`.
6. Commit the new tests to this branch.
7. Push the branch and create a Pull Request into `${PR_BRANCH}`.
- Title: "chore: improve test coverage"
- Body: "Closes #<issue_number>."

47 changes: 47 additions & 0 deletions .github/copilot-instructions.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,47 @@
# Rhiza Copilot Instructions

You are working in a project that utilizes the `rhiza` framework. Rhiza is a collection of reusable configuration templates and tooling designed to standardize and streamline modern Python development.

As a Rhiza-based project, this workspace adheres to specific conventions for structure, dependency management, and automation.

## Development Environment

The project uses `make` and `uv` for development tasks.

- **Install Dependencies**: `make install` (installs `uv`, creates `.venv`, installs dependencies)
- **Run Tests**: `make test` (runs `pytest` with coverage)
- **Format Code**: `make fmt` (runs `ruff format` and `ruff check --fix`)
- **Check Dependencies**: `make deptry` (runs `deptry` to check for missing/unused dependencies)
- **Marimo Notebooks**: `make marimo` (starts the Marimo server)
- **Build Documentation**: `make book` (builds the documentation book)

## Project Structure

- `src/`: Source code
- `tests/`: Tests (pytest)
- `assets/`: Static assets
- `book/`: Documentation source
- `docker/`: Docker configuration
- `presentation/`: Presentation slides
- `.rhiza/`: Rhiza-specific scripts and configurations

## Coding Standards

- **Style**: Follow PEP 8. Use `make fmt` to enforce style.
- **Testing**: Write tests in `tests/` using `pytest`. Ensure high coverage.
- **Documentation**: Document code using docstrings.
- **Dependencies**: Manage dependencies in `pyproject.toml`. Use `uv add` to add dependencies.

## Workflow

1. **Setup**: Run `make install` to set up the environment.
2. **Develop**: Write code in `src/` and tests in `tests/`.
3. **Test**: Run `make test` to verify changes.
4. **Format**: Run `make fmt` before committing.
5. **Verify**: Run `make deptry` to check dependencies.

## Key Files

- `Makefile`: Main entry point for tasks.
- `pyproject.toml`: Project configuration and dependencies.
- `.devcontainer/bootstrap.sh`: Bootstrap script for dev containers.
44 changes: 44 additions & 0 deletions .github/workflows/copilot-setup-steps.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,44 @@
# Following the instructions at https://docs.github.com/en/copilot/how-tos/use-copilot-agents/coding-agent/customize-the-agent-environment#preinstalling-tools-or-dependencies-in-copilots-environment, create a workflow
# that defines the setup steps for Copilot Agents.

name: "Copilot Setup Steps"

# Automatically run the setup steps when they are changed to allow for easy validation, and
# allow manual testing through the repository's "Actions" tab
on:
workflow_dispatch:
push:
paths:
- .github/workflows/copilot-setup-steps.yml
pull_request:
paths:
- .github/workflows/copilot-setup-steps.yml

jobs:
# The job MUST be called `copilot-setup-steps` or it will not be picked up by Copilot.
copilot-setup-steps:
runs-on: ubuntu-latest

# Set the permissions to the lowest permissions possible needed for your steps.
# Copilot will be given its own token for its operations.
permissions:
# If you want to clone the repository as part of your setup steps, for example to install dependencies, you'll need the `contents: read` permission. If you don't clone the repository in your setup steps, Copilot will do this for you automatically after the steps complete.
contents: read

# You can define any steps you want, and they will run before the agent starts.
# If you do not check out your code, Copilot will do this for you.
steps:
- name: Checkout code
uses: actions/checkout@v4

- name: Install dependencies
run: |
make install
echo "$(pwd)/bin" >> $GITHUB_PATH

# Initialize pre-commit hooks as per bootstrap.sh
if [ -f .pre-commit-config.yaml ]; then
./bin/uvx pre-commit install
fi


139 changes: 139 additions & 0 deletions .github/workflows/rhiza_coverage-agent.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,139 @@
name: RHIZA COVERAGE AGENT

on:
pull_request:
branches: [ main, master ]

permissions:
contents: read
issues: write

jobs:
check-coverage:
runs-on: ubuntu-latest
steps:
- name: Checkout code
uses: actions/checkout@v4

- name: Install uv
uses: astral-sh/setup-uv@v1
with:
version: "latest"

- name: Set up Python
uses: actions/setup-python@v5
with:
python-version-file: ".python-version"

- name: Run tests and generate coverage
run: |
make test

- name: Check Coverage and Create Issue
env:
GH_TOKEN: ${{ secrets.GITHUB_TOKEN }}
ISSUE_ASSIGNEE: copilot
run: |
python -c "
import json
import subprocess
import sys
import os

def ensure_label(name, color, description):
# Attempt to create label. If it fails (e.g. exists), we ignore the error.
subprocess.run(
['gh', 'label', 'create', name, '--color', color, '--description', description],
capture_output=True
)

def create_issue(title, body, labels, assignee):
cmd = ['gh', 'issue', 'create', '--title', title, '--body', body]
for label in labels:
cmd.extend(['--label', label])

try:
# Create issue without assignee first to avoid failure if assignee is invalid
result = subprocess.run(cmd, capture_output=True, text=True, check=True)
print('Issue created successfully.')
issue_url = result.stdout.strip()

# Try to assign
if assignee:
print(f'Attempting to assign to {assignee}...')
assign_cmd = ['gh', 'issue', 'edit', issue_url, '--add-assignee', assignee]
assign_res = subprocess.run(assign_cmd, capture_output=True, text=True)
if assign_res.returncode != 0:
print(f'Warning: Could not assign to {assignee}: {assign_res.stderr}')
else:
print(f'Assigned to {assignee}.')
except subprocess.CalledProcessError as e:
print(f'Failed to create issue: {e.stderr}')
raise

# Ensure labels exist
ensure_label('coverage', '0E8A16', 'Related to code coverage')
ensure_label('chore', 'EDEDED', 'Routine tasks and maintenance')

def get_agent_instructions():
try:
with open('.github/agents/coverage-agent.md', 'r') as f:
content = f.read()
# Simple parsing to extract instructions block
if 'instructions: |' in content:
return content.split('instructions: |')[1].strip()
return content
except Exception:
return "Please analyze the coverage report and add tests to reach 100% coverage."

instructions = get_agent_instructions()

coverage_path = '_tests/coverage.json'
has_report = os.path.exists(coverage_path)
repo = os.environ.get('GITHUB_REPOSITORY')
is_rhiza = repo == 'jebel-quant/rhiza'
assignee = os.environ.get('ISSUE_ASSIGNEE')

print(f'Checking coverage report at {coverage_path}...')
print(f'Repository: {repo}')

if not has_report:
print('Coverage report not found.')
if is_rhiza:
print('Rhiza repository detected. Creating issue for framework test review...')
title = 'Rhiza Framework Test Review Needed'
body = (
'The coverage report was not found, but this is the Rhiza repository.\n\n'
'Please review \`tests/test_rhiza\` and ensure the framework is sufficiently tested.\n\n'
'@github-actions assign to copilot'
)
create_issue(title, body, ['coverage', 'chore'], assignee)
else:
print('Not the Rhiza repository. Skipping.')
sys.exit(0)

try:
with open(coverage_path) as f:
data = json.load(f)

coverage = data['totals']['percent_covered']
print(f'Current Coverage: {coverage:.2f}%')

if coverage < 100:
print('Coverage is below 100%. Creating GitHub Issue...')

title = f'Coverage Gap Detected: {coverage:.2f}%'
body = (
f'Coverage analysis detected gaps. Current coverage is {coverage:.2f}%.\n\n'
f'{instructions}\n\n'
'@github-actions assign to copilot'
)

create_issue(title, body, ['coverage', 'chore'], assignee)
else:
print('Coverage is 100%. No issue needed.')

except Exception as e:
print(f'Error: {e}')
sys.exit(1)
"
1 change: 1 addition & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -111,6 +111,7 @@ Meta
Development and Testing
test run all tests
benchmark run performance benchmarks
show-coverage open the coverage report in the browser

Documentation
docs create documentation with pdoc
Expand Down
16 changes: 15 additions & 1 deletion tests/Makefile.tests
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@
# This file is included by the main Makefile

# Declare phony targets (they don't produce files)
.PHONY: test benchmark
.PHONY: test benchmark show-coverage

# Test-specific variables
TESTS_FOLDER := tests
Expand All @@ -29,3 +29,17 @@ benchmark: install ## run performance benchmarks
printf "${YELLOW}[WARN] Benchmarks folder not found, skipping benchmarks${RESET}\n"; \
fi

show-coverage: ## open the coverage report in the browser
@if [ -f _tests/html-coverage/index.html ]; then \
if command -v open >/dev/null 2>&1; then \
open _tests/html-coverage/index.html; \
elif command -v xdg-open >/dev/null 2>&1; then \
xdg-open _tests/html-coverage/index.html; \
else \
printf "${YELLOW}[WARN] Could not open browser. Please open _tests/html-coverage/index.html manually.${RESET}\n"; \
fi \
else \
printf "${YELLOW}[WARN] Coverage report not found. Run 'make test' first.${RESET}\n"; \
fi


Loading