From 6b70999d3f35cb3ca629e710d01f096bb7a31221 Mon Sep 17 00:00:00 2001 From: HarryCampion Date: Sat, 3 Jan 2026 16:34:51 +0000 Subject: [PATCH 01/18] Add coverage agent and bot --- .github/agents/coverage-agent.md | 13 +++ .github/workflows/coverage-agent.yml | 49 +++++++++ .rhiza/scripts/utils/coverage_bot.py | 157 +++++++++++++++++++++++++++ coverage_report.md | 23 ++++ src/rhiza/__init__.py | 0 src/rhiza/eg.py | 2 + tests/Makefile.tests | 3 + 7 files changed, 247 insertions(+) create mode 100644 .github/agents/coverage-agent.md create mode 100644 .github/workflows/coverage-agent.yml create mode 100644 .rhiza/scripts/utils/coverage_bot.py create mode 100644 coverage_report.md create mode 100644 src/rhiza/__init__.py create mode 100644 src/rhiza/eg.py diff --git a/.github/agents/coverage-agent.md b/.github/agents/coverage-agent.md new file mode 100644 index 00000000..c05537fc --- /dev/null +++ b/.github/agents/coverage-agent.md @@ -0,0 +1,13 @@ +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. + + When given a list of files and missing lines: + 1. Analyze the source code of the files to understand the uncovered logic. + 2. Write comprehensive `pytest` test functions to cover those lines. + 3. Use existing fixtures if available, or create simple ones. + 4. Ensure the tests are syntactically correct and follow the project's style. + 5. Do not modify the source code, only add tests in the `tests/` directory. + 6. If a test file does not exist for a source file, create it (e.g., `src/foo.py` -> `tests/test_foo.py`). diff --git a/.github/workflows/coverage-agent.yml b/.github/workflows/coverage-agent.yml new file mode 100644 index 00000000..e75378ae --- /dev/null +++ b/.github/workflows/coverage-agent.yml @@ -0,0 +1,49 @@ +name: Coverage Agent + +on: + pull_request: + branches: [ main, master ] + +permissions: + contents: read + pull-requests: 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: Install dependencies + run: | + uv sync --all-extras --dev + uv pip install openai + + - name: Run tests and generate coverage + run: | + make test + + - name: Run Coverage Bot + id: coverage-bot + env: + GH_TOKEN: ${{ secrets.GITHUB_TOKEN }} + run: | + uv run python .rhiza/scripts/utils/coverage_bot.py + + - name: Post Coverage Comment + if: failure() && github.event_name == 'pull_request' + env: + GH_TOKEN: ${{ secrets.GITHUB_TOKEN }} + run: | + gh pr comment ${{ github.event.pull_request.number }} --body-file coverage_report.md diff --git a/.rhiza/scripts/utils/coverage_bot.py b/.rhiza/scripts/utils/coverage_bot.py new file mode 100644 index 00000000..b70f822e --- /dev/null +++ b/.rhiza/scripts/utils/coverage_bot.py @@ -0,0 +1,157 @@ +import json +import sys +import os +import ast +import subprocess +from pathlib import Path + +def get_function_source(file_path, node): + """Extracts the source code of the function from the file.""" + try: + with open(file_path, "r") as f: + lines = f.readlines() + # lineno is 1-based, list index is 0-based + return "".join(lines[node.lineno - 1 : node.end_lineno]) + except Exception: + return None + +def find_function_node(file_path, line_number): + """Finds the function node containing the given line number.""" + try: + with open(file_path, "r") as f: + tree = ast.parse(f.read(), filename=file_path) + except Exception: + return None + + target_node = None + + for node in ast.walk(tree): + if isinstance(node, (ast.FunctionDef, ast.AsyncFunctionDef)): + if hasattr(node, 'lineno') and hasattr(node, 'end_lineno'): + if node.lineno <= line_number <= node.end_lineno: + target_node = node + + return target_node + +def trigger_gh_agent(prompt): + """Triggers the GitHub Agent Task.""" + print("🤖 Triggering GitHub Agent Task...") + try: + # We use the custom agent defined in .github/agents/coverage-agent.md + # We pass the prompt via stdin + process = subprocess.Popen( + ["gh", "agent-task", "create", "--custom-agent", "coverage-agent", "-F", "-"], + stdin=subprocess.PIPE, + stdout=subprocess.PIPE, + stderr=subprocess.PIPE, + text=True + ) + stdout, stderr = process.communicate(input=prompt) + + if process.returncode != 0: + if "specified custom agent not found" in stderr: + print(f"❌ Failed to trigger agent: Custom agent 'coverage-agent' not found.") + print("👉 Please ensure you have pushed the file '.github/agents/coverage-agent.md' to your remote repository.") + print(" GitHub Agent Tasks require the agent definition to be present on GitHub.") + else: + print(f"❌ Failed to trigger agent: {stderr}") + else: + print(f"✅ Agent task started successfully!\n{stdout}") + + except FileNotFoundError: + print("❌ 'gh' CLI not found. Ensure GitHub CLI is installed and available in PATH.") + +def main(): + # Search for coverage.json in common locations + possible_paths = [ + Path("coverage.json"), + Path("_tests/coverage.json"), + Path("tests/coverage.json"), + Path(".coverage.json") + ] + + coverage_file = None + for path in possible_paths: + if path.exists(): + coverage_file = path + break + + if not coverage_file: + # Fallback: try to find it anywhere + try: + found = list(Path(".").rglob("coverage.json")) + if found: + coverage_file = found[0] + except Exception: + pass + + if not coverage_file or not coverage_file.exists(): + print("❌ No coverage data found.") + return + + print(f"📂 Using coverage file: {coverage_file}") + + try: + with open(coverage_file, "r") as f: + data = json.load(f) + except json.JSONDecodeError: + print("❌ Invalid coverage JSON.") + return + + totals = data.get("totals", {}) + covered_lines = totals.get("covered_lines", 0) + num_statements = totals.get("num_statements", 0) + + if num_statements == 0: + percent_covered = 100.0 + else: + percent_covered = (covered_lines / num_statements) * 100 + + print(f"📊 Total Coverage: {percent_covered:.2f}%") + + if percent_covered >= 100: + print("✅ Coverage is 100%. Great job!") + sys.exit(0) + + # Prepare prompt for the agent + agent_prompt = [] + agent_prompt.append(f"The current code coverage is {percent_covered:.2f}%, which is below the target of 100%.") + agent_prompt.append("Please write pytest tests to cover the following missing lines:\n") + + files = data.get("files", {}) + has_missing = False + + for filename, file_data in files.items(): + missing_lines = file_data.get("missing_lines", []) + if not missing_lines: + continue + + has_missing = True + agent_prompt.append(f"File: {filename}") + agent_prompt.append(f"Missing lines: {', '.join(map(str, missing_lines))}") + + # Provide context about the functions + if os.path.exists(filename): + functions_to_test = {} + for line in missing_lines: + node = find_function_node(filename, line) + if node and node.name not in functions_to_test: + functions_to_test[node.name] = get_function_source(filename, node) + + if functions_to_test: + agent_prompt.append("Relevant functions source code:") + for func_name, func_source in functions_to_test.items(): + agent_prompt.append(f"```python\n# Function: {func_name}\n{func_source}\n```") + + agent_prompt.append("-" * 20) + + if has_missing: + full_prompt = "\n".join(agent_prompt) + # Trigger the agent + trigger_gh_agent(full_prompt) + + # Fail the build so the user knows coverage is low (and agent is working on it) + sys.exit(1) + +if __name__ == "__main__": + main() diff --git a/coverage_report.md b/coverage_report.md new file mode 100644 index 00000000..9f5a9fde --- /dev/null +++ b/coverage_report.md @@ -0,0 +1,23 @@ +## 🤖 Coverage Agent Report +**Current Coverage:** 50.00% (Target: 100%) + +> â„šī¸ **Note:** Add `OPENAI_API_KEY` to your repository secrets to enable AI-generated tests. Currently showing stubs. + +I found some gaps in your code coverage. Here are suggested tests to fill them: + +### 📄 `src/rhiza/eg.py` +Missing lines: 2 + +**Suggested Tests:** +```python + +def test_x_coverage(): + # TODO: Import the module correctly based on your project structure + from src.rhiza.eg import x + + # TODO: Add test parameters to cover missing lines in x + # result = x(...) + # assert result is not None + + +``` \ No newline at end of file diff --git a/src/rhiza/__init__.py b/src/rhiza/__init__.py new file mode 100644 index 00000000..e69de29b diff --git a/src/rhiza/eg.py b/src/rhiza/eg.py new file mode 100644 index 00000000..d30f6e70 --- /dev/null +++ b/src/rhiza/eg.py @@ -0,0 +1,2 @@ +def x(): + return 1 \ No newline at end of file diff --git a/tests/Makefile.tests b/tests/Makefile.tests index 141fe90c..187a54c8 100644 --- a/tests/Makefile.tests +++ b/tests/Makefile.tests @@ -29,3 +29,6 @@ benchmark: install ## run performance benchmarks printf "${YELLOW}[WARN] Benchmarks folder not found, skipping benchmarks${RESET}\n"; \ fi +coverage-bot: test ## run the coverage bot to suggest tests + @${UV_BIN} run python .rhiza/scripts/utils/coverage_bot.py + From 2d93970821a4cf04301821df9b47fa93fe7f18c4 Mon Sep 17 00:00:00 2001 From: HarryCampion Date: Sat, 3 Jan 2026 17:07:54 +0000 Subject: [PATCH 02/18] simplify coverage bot & helper for coverage visuals --- .github/agents/coverage-agent.md | 18 +- ...age-agent.yml => rhiza_coverage-agent.yml} | 28 ++-- .rhiza/scripts/utils/coverage_bot.py | 157 ------------------ tests/Makefile.tests | 11 +- 4 files changed, 30 insertions(+), 184 deletions(-) rename .github/workflows/{coverage-agent.yml => rhiza_coverage-agent.yml} (52%) delete mode 100644 .rhiza/scripts/utils/coverage_bot.py diff --git a/.github/agents/coverage-agent.md b/.github/agents/coverage-agent.md index c05537fc..046dfda5 100644 --- a/.github/agents/coverage-agent.md +++ b/.github/agents/coverage-agent.md @@ -4,10 +4,14 @@ 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. - When given a list of files and missing lines: - 1. Analyze the source code of the files to understand the uncovered logic. - 2. Write comprehensive `pytest` test functions to cover those lines. - 3. Use existing fixtures if available, or create simple ones. - 4. Ensure the tests are syntactically correct and follow the project's style. - 5. Do not modify the source code, only add tests in the `tests/` directory. - 6. If a test file does not exist for a source file, create it (e.g., `src/foo.py` -> `tests/test_foo.py`). + 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. Create a new Issue titled "Coverage Gap Detected" describing the gaps. + 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 #." + diff --git a/.github/workflows/coverage-agent.yml b/.github/workflows/rhiza_coverage-agent.yml similarity index 52% rename from .github/workflows/coverage-agent.yml rename to .github/workflows/rhiza_coverage-agent.yml index e75378ae..4bc7d84d 100644 --- a/.github/workflows/coverage-agent.yml +++ b/.github/workflows/rhiza_coverage-agent.yml @@ -1,12 +1,13 @@ -name: Coverage Agent +name: RHIZA COVERAGE AGENT on: pull_request: branches: [ main, master ] permissions: - contents: read + contents: write pull-requests: write + issues: write jobs: check-coverage: @@ -25,25 +26,18 @@ jobs: with: python-version-file: ".python-version" - - name: Install dependencies - run: | - uv sync --all-extras --dev - uv pip install openai - - name: Run tests and generate coverage run: | make test - - name: Run Coverage Bot - id: coverage-bot - env: - GH_TOKEN: ${{ secrets.GITHUB_TOKEN }} - run: | - uv run python .rhiza/scripts/utils/coverage_bot.py - - - name: Post Coverage Comment - if: failure() && github.event_name == 'pull_request' + - name: Run Agent to Fix Coverage env: GH_TOKEN: ${{ secrets.GITHUB_TOKEN }} + PR_BRANCH: ${{ github.head_ref || github.ref_name }} + RUN_ID: ${{ github.run_id }} run: | - gh pr comment ${{ github.event.pull_request.number }} --body-file coverage_report.md + # Create a temporary prompt file combining instructions and context + cat .github/agents/coverage-agent.md > agent_prompt.txt + echo -e "\n\nCONTEXT:\nPR_BRANCH: $PR_BRANCH\nRUN_ID: $RUN_ID" >> agent_prompt.txt + + gh agent-task create --body-file agent_prompt.txt diff --git a/.rhiza/scripts/utils/coverage_bot.py b/.rhiza/scripts/utils/coverage_bot.py deleted file mode 100644 index b70f822e..00000000 --- a/.rhiza/scripts/utils/coverage_bot.py +++ /dev/null @@ -1,157 +0,0 @@ -import json -import sys -import os -import ast -import subprocess -from pathlib import Path - -def get_function_source(file_path, node): - """Extracts the source code of the function from the file.""" - try: - with open(file_path, "r") as f: - lines = f.readlines() - # lineno is 1-based, list index is 0-based - return "".join(lines[node.lineno - 1 : node.end_lineno]) - except Exception: - return None - -def find_function_node(file_path, line_number): - """Finds the function node containing the given line number.""" - try: - with open(file_path, "r") as f: - tree = ast.parse(f.read(), filename=file_path) - except Exception: - return None - - target_node = None - - for node in ast.walk(tree): - if isinstance(node, (ast.FunctionDef, ast.AsyncFunctionDef)): - if hasattr(node, 'lineno') and hasattr(node, 'end_lineno'): - if node.lineno <= line_number <= node.end_lineno: - target_node = node - - return target_node - -def trigger_gh_agent(prompt): - """Triggers the GitHub Agent Task.""" - print("🤖 Triggering GitHub Agent Task...") - try: - # We use the custom agent defined in .github/agents/coverage-agent.md - # We pass the prompt via stdin - process = subprocess.Popen( - ["gh", "agent-task", "create", "--custom-agent", "coverage-agent", "-F", "-"], - stdin=subprocess.PIPE, - stdout=subprocess.PIPE, - stderr=subprocess.PIPE, - text=True - ) - stdout, stderr = process.communicate(input=prompt) - - if process.returncode != 0: - if "specified custom agent not found" in stderr: - print(f"❌ Failed to trigger agent: Custom agent 'coverage-agent' not found.") - print("👉 Please ensure you have pushed the file '.github/agents/coverage-agent.md' to your remote repository.") - print(" GitHub Agent Tasks require the agent definition to be present on GitHub.") - else: - print(f"❌ Failed to trigger agent: {stderr}") - else: - print(f"✅ Agent task started successfully!\n{stdout}") - - except FileNotFoundError: - print("❌ 'gh' CLI not found. Ensure GitHub CLI is installed and available in PATH.") - -def main(): - # Search for coverage.json in common locations - possible_paths = [ - Path("coverage.json"), - Path("_tests/coverage.json"), - Path("tests/coverage.json"), - Path(".coverage.json") - ] - - coverage_file = None - for path in possible_paths: - if path.exists(): - coverage_file = path - break - - if not coverage_file: - # Fallback: try to find it anywhere - try: - found = list(Path(".").rglob("coverage.json")) - if found: - coverage_file = found[0] - except Exception: - pass - - if not coverage_file or not coverage_file.exists(): - print("❌ No coverage data found.") - return - - print(f"📂 Using coverage file: {coverage_file}") - - try: - with open(coverage_file, "r") as f: - data = json.load(f) - except json.JSONDecodeError: - print("❌ Invalid coverage JSON.") - return - - totals = data.get("totals", {}) - covered_lines = totals.get("covered_lines", 0) - num_statements = totals.get("num_statements", 0) - - if num_statements == 0: - percent_covered = 100.0 - else: - percent_covered = (covered_lines / num_statements) * 100 - - print(f"📊 Total Coverage: {percent_covered:.2f}%") - - if percent_covered >= 100: - print("✅ Coverage is 100%. Great job!") - sys.exit(0) - - # Prepare prompt for the agent - agent_prompt = [] - agent_prompt.append(f"The current code coverage is {percent_covered:.2f}%, which is below the target of 100%.") - agent_prompt.append("Please write pytest tests to cover the following missing lines:\n") - - files = data.get("files", {}) - has_missing = False - - for filename, file_data in files.items(): - missing_lines = file_data.get("missing_lines", []) - if not missing_lines: - continue - - has_missing = True - agent_prompt.append(f"File: {filename}") - agent_prompt.append(f"Missing lines: {', '.join(map(str, missing_lines))}") - - # Provide context about the functions - if os.path.exists(filename): - functions_to_test = {} - for line in missing_lines: - node = find_function_node(filename, line) - if node and node.name not in functions_to_test: - functions_to_test[node.name] = get_function_source(filename, node) - - if functions_to_test: - agent_prompt.append("Relevant functions source code:") - for func_name, func_source in functions_to_test.items(): - agent_prompt.append(f"```python\n# Function: {func_name}\n{func_source}\n```") - - agent_prompt.append("-" * 20) - - if has_missing: - full_prompt = "\n".join(agent_prompt) - # Trigger the agent - trigger_gh_agent(full_prompt) - - # Fail the build so the user knows coverage is low (and agent is working on it) - sys.exit(1) - -if __name__ == "__main__": - main() diff --git a/tests/Makefile.tests b/tests/Makefile.tests index 187a54c8..30ffd2ef 100644 --- a/tests/Makefile.tests +++ b/tests/Makefile.tests @@ -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 @@ -29,6 +29,11 @@ benchmark: install ## run performance benchmarks printf "${YELLOW}[WARN] Benchmarks folder not found, skipping benchmarks${RESET}\n"; \ fi -coverage-bot: test ## run the coverage bot to suggest tests - @${UV_BIN} run python .rhiza/scripts/utils/coverage_bot.py +show-coverage: test ## open the coverage report in the browser + @if [ -f _tests/html-coverage/index.html ]; then \ + open _tests/html-coverage/index.html; \ + else \ + printf "${YELLOW}[WARN] Coverage report not found. Run 'make test' first.${RESET}\n"; \ + fi + From 39b193c0ed795f617c8e474f2324973340b6d00e Mon Sep 17 00:00:00 2001 From: HarryCampion Date: Sat, 3 Jan 2026 17:08:57 +0000 Subject: [PATCH 03/18] make fmt --- README.md | 1 + src/rhiza/eg.py | 2 +- 2 files changed, 2 insertions(+), 1 deletion(-) diff --git a/README.md b/README.md index 55fd19cf..a5fcd599 100644 --- a/README.md +++ b/README.md @@ -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 diff --git a/src/rhiza/eg.py b/src/rhiza/eg.py index d30f6e70..cba1a263 100644 --- a/src/rhiza/eg.py +++ b/src/rhiza/eg.py @@ -1,2 +1,2 @@ def x(): - return 1 \ No newline at end of file + return 1 From 6c80e005298f8b85811c8202f7fce8f1c18a7cc0 Mon Sep 17 00:00:00 2001 From: HarryCampion Date: Sat, 3 Jan 2026 17:09:45 +0000 Subject: [PATCH 04/18] remove report --- coverage_report.md | 23 ----------------------- 1 file changed, 23 deletions(-) delete mode 100644 coverage_report.md diff --git a/coverage_report.md b/coverage_report.md deleted file mode 100644 index 9f5a9fde..00000000 --- a/coverage_report.md +++ /dev/null @@ -1,23 +0,0 @@ -## 🤖 Coverage Agent Report -**Current Coverage:** 50.00% (Target: 100%) - -> â„šī¸ **Note:** Add `OPENAI_API_KEY` to your repository secrets to enable AI-generated tests. Currently showing stubs. - -I found some gaps in your code coverage. Here are suggested tests to fill them: - -### 📄 `src/rhiza/eg.py` -Missing lines: 2 - -**Suggested Tests:** -```python - -def test_x_coverage(): - # TODO: Import the module correctly based on your project structure - from src.rhiza.eg import x - - # TODO: Add test parameters to cover missing lines in x - # result = x(...) - # assert result is not None - - -``` \ No newline at end of file From 1b72c5e704509355ac396b645e204aeadf7b1ae7 Mon Sep 17 00:00:00 2001 From: HarryCampion Date: Sat, 3 Jan 2026 17:11:02 +0000 Subject: [PATCH 05/18] do not run make tests first --- tests/Makefile.tests | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/tests/Makefile.tests b/tests/Makefile.tests index 30ffd2ef..6c9a1510 100644 --- a/tests/Makefile.tests +++ b/tests/Makefile.tests @@ -29,7 +29,7 @@ benchmark: install ## run performance benchmarks printf "${YELLOW}[WARN] Benchmarks folder not found, skipping benchmarks${RESET}\n"; \ fi -show-coverage: test ## open the coverage report in the browser +show-coverage: ## open the coverage report in the browser @if [ -f _tests/html-coverage/index.html ]; then \ open _tests/html-coverage/index.html; \ else \ From 34a97151192d802588505bf83194521d10f80fce Mon Sep 17 00:00:00 2001 From: HarryCampion Date: Sat, 3 Jan 2026 17:12:05 +0000 Subject: [PATCH 06/18] more resilience --- tests/Makefile.tests | 8 +++++++- 1 file changed, 7 insertions(+), 1 deletion(-) diff --git a/tests/Makefile.tests b/tests/Makefile.tests index 6c9a1510..45fcdfda 100644 --- a/tests/Makefile.tests +++ b/tests/Makefile.tests @@ -31,7 +31,13 @@ benchmark: install ## run performance benchmarks show-coverage: ## open the coverage report in the browser @if [ -f _tests/html-coverage/index.html ]; then \ - open _tests/html-coverage/index.html; \ + 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 From 935692f45520c0a48fdde97bb7baabc03d2cf417 Mon Sep 17 00:00:00 2001 From: HarryCampion Date: Sat, 3 Jan 2026 17:16:21 +0000 Subject: [PATCH 07/18] more resilience --- .github/agents/coverage-agent.md | 2 ++ .github/workflows/coverage-agent.yml | 52 ++++++++++++++++++++++++++++ 2 files changed, 54 insertions(+) create mode 100644 .github/workflows/coverage-agent.yml diff --git a/.github/agents/coverage-agent.md b/.github/agents/coverage-agent.md index 046dfda5..73a045b7 100644 --- a/.github/agents/coverage-agent.md +++ b/.github/agents/coverage-agent.md @@ -4,6 +4,8 @@ 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. diff --git a/.github/workflows/coverage-agent.yml b/.github/workflows/coverage-agent.yml new file mode 100644 index 00000000..22fbfed3 --- /dev/null +++ b/.github/workflows/coverage-agent.yml @@ -0,0 +1,52 @@ +name: RHIZA COVERAGE AGENT + +on: + pull_request: + branches: [ main, master ] + +permissions: + contents: write + pull-requests: write + 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: Install dependencies + run: | + uv sync --all-extras --dev + uv pip install openai + + - name: Run tests and generate coverage + run: | + make test + + - name: Run Agent to Fix Coverage + env: + GH_TOKEN: ${{ secrets.GITHUB_TOKEN }} + PR_BRANCH: ${{ github.head_ref || github.ref_name }} + RUN_ID: ${{ github.run_id }} + run: | + # Create a task description file with context + echo "Please follow the instructions defined in the agent definition." > agent_task.txt + echo "" >> agent_task.txt + echo "CONTEXT:" >> agent_task.txt + echo "PR_BRANCH: $PR_BRANCH" >> agent_task.txt + echo "RUN_ID: $RUN_ID" >> agent_task.txt + + # Run the agent task using the defined coverage-agent + gh agent-task create -a coverage-agent -F agent_task.txt From 9109d7fff0ebea55dbc04edf81178650f7880b6c Mon Sep 17 00:00:00 2001 From: HarryCampion Date: Sat, 3 Jan 2026 17:24:09 +0000 Subject: [PATCH 08/18] delete old --- .github/workflows/coverage-agent.yml | 52 ---------------------------- 1 file changed, 52 deletions(-) delete mode 100644 .github/workflows/coverage-agent.yml diff --git a/.github/workflows/coverage-agent.yml b/.github/workflows/coverage-agent.yml deleted file mode 100644 index 22fbfed3..00000000 --- a/.github/workflows/coverage-agent.yml +++ /dev/null @@ -1,52 +0,0 @@ -name: RHIZA COVERAGE AGENT - -on: - pull_request: - branches: [ main, master ] - -permissions: - contents: write - pull-requests: write - 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: Install dependencies - run: | - uv sync --all-extras --dev - uv pip install openai - - - name: Run tests and generate coverage - run: | - make test - - - name: Run Agent to Fix Coverage - env: - GH_TOKEN: ${{ secrets.GITHUB_TOKEN }} - PR_BRANCH: ${{ github.head_ref || github.ref_name }} - RUN_ID: ${{ github.run_id }} - run: | - # Create a task description file with context - echo "Please follow the instructions defined in the agent definition." > agent_task.txt - echo "" >> agent_task.txt - echo "CONTEXT:" >> agent_task.txt - echo "PR_BRANCH: $PR_BRANCH" >> agent_task.txt - echo "RUN_ID: $RUN_ID" >> agent_task.txt - - # Run the agent task using the defined coverage-agent - gh agent-task create -a coverage-agent -F agent_task.txt From 1d07e1d0b3835ef793f4fcf574a95a3a39d8df87 Mon Sep 17 00:00:00 2001 From: HarryCampion Date: Sat, 3 Jan 2026 17:29:37 +0000 Subject: [PATCH 09/18] add repository level copilot instructions --- .github/copilot-instructions.md | 45 +++++++++++++++++++++++ .github/workflows/copilot-setup-steps.yml | 44 ++++++++++++++++++++++ 2 files changed, 89 insertions(+) create mode 100644 .github/copilot-instructions.md create mode 100644 .github/workflows/copilot-setup-steps.yml diff --git a/.github/copilot-instructions.md b/.github/copilot-instructions.md new file mode 100644 index 00000000..acd853f0 --- /dev/null +++ b/.github/copilot-instructions.md @@ -0,0 +1,45 @@ +# Rhiza Copilot Instructions + +You are working in the `rhiza` project, a collection of reusable configuration templates for modern Python projects. + +## 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. diff --git a/.github/workflows/copilot-setup-steps.yml b/.github/workflows/copilot-setup-steps.yml new file mode 100644 index 00000000..4d8c5918 --- /dev/null +++ b/.github/workflows/copilot-setup-steps.yml @@ -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 + + From d021d27099b52423b56b0569c995cf44e42be137 Mon Sep 17 00:00:00 2001 From: HarryCampion Date: Sat, 3 Jan 2026 17:30:30 +0000 Subject: [PATCH 10/18] remove tmp stuff --- src/rhiza/__init__.py | 0 src/rhiza/eg.py | 2 -- 2 files changed, 2 deletions(-) delete mode 100644 src/rhiza/__init__.py delete mode 100644 src/rhiza/eg.py diff --git a/src/rhiza/__init__.py b/src/rhiza/__init__.py deleted file mode 100644 index e69de29b..00000000 diff --git a/src/rhiza/eg.py b/src/rhiza/eg.py deleted file mode 100644 index cba1a263..00000000 --- a/src/rhiza/eg.py +++ /dev/null @@ -1,2 +0,0 @@ -def x(): - return 1 From 9950da2523a512ec288135b4c52566493db7aeff Mon Sep 17 00:00:00 2001 From: HarryCampion Date: Sat, 3 Jan 2026 17:32:30 +0000 Subject: [PATCH 11/18] fix --- .github/workflows/rhiza_coverage-agent.yml | 14 +++++++++----- 1 file changed, 9 insertions(+), 5 deletions(-) diff --git a/.github/workflows/rhiza_coverage-agent.yml b/.github/workflows/rhiza_coverage-agent.yml index 4bc7d84d..57ba11d1 100644 --- a/.github/workflows/rhiza_coverage-agent.yml +++ b/.github/workflows/rhiza_coverage-agent.yml @@ -32,12 +32,16 @@ jobs: - name: Run Agent to Fix Coverage env: - GH_TOKEN: ${{ secrets.GITHUB_TOKEN }} + GH_TOKEN: ${{ secrets.GH_PAT }} PR_BRANCH: ${{ github.head_ref || github.ref_name }} RUN_ID: ${{ github.run_id }} run: | - # Create a temporary prompt file combining instructions and context - cat .github/agents/coverage-agent.md > agent_prompt.txt - echo -e "\n\nCONTEXT:\nPR_BRANCH: $PR_BRANCH\nRUN_ID: $RUN_ID" >> agent_prompt.txt + # Create a task description file with context + echo "Please follow the instructions defined in the agent definition." > agent_task.txt + echo "" >> agent_task.txt + echo "CONTEXT:" >> agent_task.txt + echo "PR_BRANCH: $PR_BRANCH" >> agent_task.txt + echo "RUN_ID: $RUN_ID" >> agent_task.txt - gh agent-task create --body-file agent_prompt.txt + # Run the agent task using the defined coverage-agent + gh agent-task create -a coverage-agent -F agent_task.txt From d61f994e330d4248b48ca19558506cd411099c9b Mon Sep 17 00:00:00 2001 From: HarryCampion Date: Sat, 3 Jan 2026 17:40:45 +0000 Subject: [PATCH 12/18] simplify --- .github/workflows/rhiza_coverage-agent.yml | 57 ++++++++++++++++------ 1 file changed, 43 insertions(+), 14 deletions(-) diff --git a/.github/workflows/rhiza_coverage-agent.yml b/.github/workflows/rhiza_coverage-agent.yml index 57ba11d1..93bd6891 100644 --- a/.github/workflows/rhiza_coverage-agent.yml +++ b/.github/workflows/rhiza_coverage-agent.yml @@ -5,8 +5,7 @@ on: branches: [ main, master ] permissions: - contents: write - pull-requests: write + contents: read issues: write jobs: @@ -30,18 +29,48 @@ jobs: run: | make test - - name: Run Agent to Fix Coverage + - name: Check Coverage and Create Issue env: - GH_TOKEN: ${{ secrets.GH_PAT }} - PR_BRANCH: ${{ github.head_ref || github.ref_name }} - RUN_ID: ${{ github.run_id }} + GH_TOKEN: ${{ secrets.GITHUB_TOKEN }} run: | - # Create a task description file with context - echo "Please follow the instructions defined in the agent definition." > agent_task.txt - echo "" >> agent_task.txt - echo "CONTEXT:" >> agent_task.txt - echo "PR_BRANCH: $PR_BRANCH" >> agent_task.txt - echo "RUN_ID: $RUN_ID" >> agent_task.txt + python -c " + import json + import subprocess + import sys - # Run the agent task using the defined coverage-agent - gh agent-task create -a coverage-agent -F agent_task.txt + try: + with open('_tests/coverage.json') 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' + 'Please analyze the coverage report and add tests to reach 100% coverage.\n\n' + '@github-actions assign to copilot' + ) + + cmd = [ + 'gh', 'issue', 'create', + '--title', title, + '--body', body, + '--label', 'coverage' + ] + + subprocess.run(cmd, check=True) + print('Issue created successfully.') + else: + print('Coverage is 100%. No issue needed.') + + except FileNotFoundError: + print('Error: _tests/coverage.json not found. Did make test run?') + sys.exit(1) + except Exception as e: + print(f'Error: {e}') + sys.exit(1) + " From 9ca1add0a005705b80ec7c2c5c275ceceb37caa2 Mon Sep 17 00:00:00 2001 From: HarryCampion Date: Sat, 3 Jan 2026 17:46:22 +0000 Subject: [PATCH 13/18] improve logic --- .github/workflows/rhiza_coverage-agent.yml | 44 ++++++++++++++++++++-- 1 file changed, 41 insertions(+), 3 deletions(-) diff --git a/.github/workflows/rhiza_coverage-agent.yml b/.github/workflows/rhiza_coverage-agent.yml index 93bd6891..410921d3 100644 --- a/.github/workflows/rhiza_coverage-agent.yml +++ b/.github/workflows/rhiza_coverage-agent.yml @@ -29,14 +29,55 @@ jobs: run: | make test + - name: Check Execution Conditions + id: check + run: | + if [ -f "_tests/coverage.json" ]; then + echo "has_report=true" >> $GITHUB_OUTPUT + else + echo "has_report=false" >> $GITHUB_OUTPUT + fi + + if [ -d "tests/test_rhiza" ]; then + echo "is_rhiza=true" >> $GITHUB_OUTPUT + else + echo "is_rhiza=false" >> $GITHUB_OUTPUT + fi + - name: Check Coverage and Create Issue + if: steps.check.outputs.has_report == 'true' || steps.check.outputs.is_rhiza == 'true' env: GH_TOKEN: ${{ secrets.GITHUB_TOKEN }} + HAS_REPORT: ${{ steps.check.outputs.has_report }} + IS_RHIZA: ${{ steps.check.outputs.is_rhiza }} run: | python -c " import json import subprocess import sys + import os + + has_report = os.environ.get('HAS_REPORT') == 'true' + is_rhiza = os.environ.get('IS_RHIZA') == 'true' + + if not has_report: + 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' + ) + cmd = [ + 'gh', 'issue', 'create', + '--title', title, + '--body', body, + '--label', 'coverage' + ] + subprocess.run(cmd, check=True) + print('Issue created successfully.') + sys.exit(0) try: with open('_tests/coverage.json') as f: @@ -67,9 +108,6 @@ jobs: else: print('Coverage is 100%. No issue needed.') - except FileNotFoundError: - print('Error: _tests/coverage.json not found. Did make test run?') - sys.exit(1) except Exception as e: print(f'Error: {e}') sys.exit(1) From 64618aec327b06f1faeb585d2490b9d1c22ae7a9 Mon Sep 17 00:00:00 2001 From: HarryCampion Date: Sat, 3 Jan 2026 17:48:58 +0000 Subject: [PATCH 14/18] add-labels --- .github/workflows/rhiza_coverage-agent.yml | 17 +++++++++++++++-- 1 file changed, 15 insertions(+), 2 deletions(-) diff --git a/.github/workflows/rhiza_coverage-agent.yml b/.github/workflows/rhiza_coverage-agent.yml index 410921d3..e5c422cd 100644 --- a/.github/workflows/rhiza_coverage-agent.yml +++ b/.github/workflows/rhiza_coverage-agent.yml @@ -57,6 +57,17 @@ jobs: 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 + ) + + # Ensure labels exist + ensure_label('coverage', '0E8A16', 'Related to code coverage') + ensure_label('chore', 'EDEDED', 'Routine tasks and maintenance') + has_report = os.environ.get('HAS_REPORT') == 'true' is_rhiza = os.environ.get('IS_RHIZA') == 'true' @@ -73,7 +84,8 @@ jobs: 'gh', 'issue', 'create', '--title', title, '--body', body, - '--label', 'coverage' + '--label', 'coverage', + '--label', 'chore' ] subprocess.run(cmd, check=True) print('Issue created successfully.') @@ -100,7 +112,8 @@ jobs: 'gh', 'issue', 'create', '--title', title, '--body', body, - '--label', 'coverage' + '--label', 'coverage', + '--label', 'chore' ] subprocess.run(cmd, check=True) From 8ba40eb51d04299048d59739f00f689a81bdb3ea Mon Sep 17 00:00:00 2001 From: HarryCampion Date: Sat, 3 Jan 2026 17:52:29 +0000 Subject: [PATCH 15/18] add-assignee --- .github/agents/coverage-agent.md | 2 +- .github/workflows/rhiza_coverage-agent.yml | 22 +++++++++++++++++++--- 2 files changed, 20 insertions(+), 4 deletions(-) diff --git a/.github/agents/coverage-agent.md b/.github/agents/coverage-agent.md index 73a045b7..d2cea718 100644 --- a/.github/agents/coverage-agent.md +++ b/.github/agents/coverage-agent.md @@ -10,7 +10,7 @@ instructions: | 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. Create a new Issue titled "Coverage Gap Detected" describing the gaps. + 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}`. diff --git a/.github/workflows/rhiza_coverage-agent.yml b/.github/workflows/rhiza_coverage-agent.yml index e5c422cd..8fd10b36 100644 --- a/.github/workflows/rhiza_coverage-agent.yml +++ b/.github/workflows/rhiza_coverage-agent.yml @@ -50,6 +50,7 @@ jobs: GH_TOKEN: ${{ secrets.GITHUB_TOKEN }} HAS_REPORT: ${{ steps.check.outputs.has_report }} IS_RHIZA: ${{ steps.check.outputs.is_rhiza }} + ISSUE_ASSIGNEE: ${{ github.actor }} run: | python -c " import json @@ -68,8 +69,21 @@ jobs: 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() has_report = os.environ.get('HAS_REPORT') == 'true' is_rhiza = os.environ.get('IS_RHIZA') == 'true' + assignee = os.environ.get('ISSUE_ASSIGNEE') if not has_report: if is_rhiza: @@ -85,7 +99,8 @@ jobs: '--title', title, '--body', body, '--label', 'coverage', - '--label', 'chore' + '--label', 'chore', + '--assignee', assignee ] subprocess.run(cmd, check=True) print('Issue created successfully.') @@ -104,7 +119,7 @@ jobs: title = f'Coverage Gap Detected: {coverage:.2f}%' body = ( f'Coverage analysis detected gaps. Current coverage is {coverage:.2f}%.\n\n' - 'Please analyze the coverage report and add tests to reach 100% coverage.\n\n' + f'{instructions}\n\n' '@github-actions assign to copilot' ) @@ -113,7 +128,8 @@ jobs: '--title', title, '--body', body, '--label', 'coverage', - '--label', 'chore' + '--label', 'chore', + '--assignee', assignee ] subprocess.run(cmd, check=True) From 43e8e334a9906509fdbf11b7d63340b7792095cc Mon Sep 17 00:00:00 2001 From: HarryCampion Date: Sat, 3 Jan 2026 17:55:23 +0000 Subject: [PATCH 16/18] add-copilot assignee --- .github/workflows/rhiza_coverage-agent.yml | 49 ++++++++++++---------- 1 file changed, 27 insertions(+), 22 deletions(-) diff --git a/.github/workflows/rhiza_coverage-agent.yml b/.github/workflows/rhiza_coverage-agent.yml index 8fd10b36..47b15bab 100644 --- a/.github/workflows/rhiza_coverage-agent.yml +++ b/.github/workflows/rhiza_coverage-agent.yml @@ -50,7 +50,7 @@ jobs: GH_TOKEN: ${{ secrets.GITHUB_TOKEN }} HAS_REPORT: ${{ steps.check.outputs.has_report }} IS_RHIZA: ${{ steps.check.outputs.is_rhiza }} - ISSUE_ASSIGNEE: ${{ github.actor }} + ISSUE_ASSIGNEE: copilot run: | python -c " import json @@ -65,6 +65,30 @@ jobs: 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') @@ -94,16 +118,7 @@ jobs: 'Please review \`tests/test_rhiza\` and ensure the framework is sufficiently tested.\n\n' '@github-actions assign to copilot' ) - cmd = [ - 'gh', 'issue', 'create', - '--title', title, - '--body', body, - '--label', 'coverage', - '--label', 'chore', - '--assignee', assignee - ] - subprocess.run(cmd, check=True) - print('Issue created successfully.') + create_issue(title, body, ['coverage', 'chore'], assignee) sys.exit(0) try: @@ -123,17 +138,7 @@ jobs: '@github-actions assign to copilot' ) - cmd = [ - 'gh', 'issue', 'create', - '--title', title, - '--body', body, - '--label', 'coverage', - '--label', 'chore', - '--assignee', assignee - ] - - subprocess.run(cmd, check=True) - print('Issue created successfully.') + create_issue(title, body, ['coverage', 'chore'], assignee) else: print('Coverage is 100%. No issue needed.') From 36bd9bd051550d806163da14ac15d6382735fa80 Mon Sep 17 00:00:00 2001 From: HarryCampion Date: Sat, 3 Jan 2026 18:00:10 +0000 Subject: [PATCH 17/18] . --- .github/workflows/rhiza_coverage-agent.yml | 33 ++++++++-------------- 1 file changed, 12 insertions(+), 21 deletions(-) diff --git a/.github/workflows/rhiza_coverage-agent.yml b/.github/workflows/rhiza_coverage-agent.yml index 47b15bab..7e86f7fc 100644 --- a/.github/workflows/rhiza_coverage-agent.yml +++ b/.github/workflows/rhiza_coverage-agent.yml @@ -29,27 +29,9 @@ jobs: run: | make test - - name: Check Execution Conditions - id: check - run: | - if [ -f "_tests/coverage.json" ]; then - echo "has_report=true" >> $GITHUB_OUTPUT - else - echo "has_report=false" >> $GITHUB_OUTPUT - fi - - if [ -d "tests/test_rhiza" ]; then - echo "is_rhiza=true" >> $GITHUB_OUTPUT - else - echo "is_rhiza=false" >> $GITHUB_OUTPUT - fi - - name: Check Coverage and Create Issue - if: steps.check.outputs.has_report == 'true' || steps.check.outputs.is_rhiza == 'true' env: GH_TOKEN: ${{ secrets.GITHUB_TOKEN }} - HAS_REPORT: ${{ steps.check.outputs.has_report }} - IS_RHIZA: ${{ steps.check.outputs.is_rhiza }} ISSUE_ASSIGNEE: copilot run: | python -c " @@ -105,11 +87,18 @@ jobs: return "Please analyze the coverage report and add tests to reach 100% coverage." instructions = get_agent_instructions() - has_report = os.environ.get('HAS_REPORT') == 'true' - is_rhiza = os.environ.get('IS_RHIZA') == 'true' + + 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' @@ -119,10 +108,12 @@ jobs: '@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('_tests/coverage.json') as f: + with open(coverage_path) as f: data = json.load(f) coverage = data['totals']['percent_covered'] From 1588ccf4d0d48551dc295175d169005c2b404a09 Mon Sep 17 00:00:00 2001 From: HarryCampion Date: Sat, 3 Jan 2026 18:49:13 +0000 Subject: [PATCH 18/18] improve copilot instructions --- .github/copilot-instructions.md | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/.github/copilot-instructions.md b/.github/copilot-instructions.md index acd853f0..a8487319 100644 --- a/.github/copilot-instructions.md +++ b/.github/copilot-instructions.md @@ -1,6 +1,8 @@ # Rhiza Copilot Instructions -You are working in the `rhiza` project, a collection of reusable configuration templates for modern Python projects. +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