Repository navigation
Conversation
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
📝 WalkthroughWalkthroughRepository-wide configuration, Rhiza Makefile support, GitHub workflow/template files, requirements manifests, utility scripts, docs, and tests were added or updated to standardize project bootstrap, quality checks, releases, and validation. ChangesRhiza bootstrap, workflows, and tests
Sequence Diagram(s)Skipped. Estimated code review effort🎯 5 (Critical) | ⏱️ ~120 minutes Poem
✨ Finishing Touches📝 Generate docstrings
🧪 Generate unit tests (beta)
|
There was a problem hiding this comment.
Pull request overview
This pull request updates the repository to the Rhiza template v0.18.8 and materializes the corresponding template-managed files (tooling config, Makefile API, docs scaffolding, and Rhiza validation tests) to align the repo with the latest template conventions.
Changes:
- Bumps
.rhiza/template.ymlref tov0.18.8and records the synced state in.rhiza/template.lock. - Introduces/updates repo-wide developer tooling configuration (Ruff, pytest, pre-commit, editorconfig, gitignore, Semgrep/Bandit).
- Adds a substantial Rhiza-managed Makefile framework, docs baseline, and a comprehensive
.rhiza/tests/suite to validate template integration.
Reviewed changes
Copilot reviewed 75 out of 79 changed files in this pull request and generated 3 comments.
Show a summary per file
| File | Description |
|---|---|
| ruff.toml | Adds Ruff lint/format configuration for the repo. |
| pytest.ini | Adds pytest defaults (paths, logging, markers, timeout). |
| Makefile | Switches to Rhiza-managed Makefile API include and sets key overrides. |
| docs/mkdocs-base.yml | Adds base MkDocs configuration used by Rhiza book tooling. |
| docs/index.md | Adds docs homepage content via snippet include. |
| docs/development/TESTS.md | Adds documentation for property/benchmark/stress testing approach. |
| docs/development/MARIMO.md | Adds documentation for Marimo notebook usage and CI integration. |
| .rhiza/utils/suppression_audit.py | Adds a utility to audit inline suppressions and report density/grade. |
| .rhiza/utils/pip_audit_policy.py | Adds a tiered pip-audit policy helper (fail runtime vulns, warn tooling). |
| .rhiza/tests/utils/test_git_repo_fixture.py | Adds tests validating the git_repo fixture behavior. |
| .rhiza/tests/test_utils.py | Adds shared helpers for Rhiza tests (make runner, ansi stripping, git setup). |
| .rhiza/tests/sync/test_readme_validation.py | Adds tests that validate runnable README code blocks (+ skip flag support). |
| .rhiza/tests/sync/test_docstrings.py | Adds doctest discovery runner across source modules (skips when absent). |
| .rhiza/tests/sync/conftest.py | Adds sync-test fixtures for isolated temp environments. |
| .rhiza/tests/structure/test_requirements.py | Adds tests enforcing .rhiza/requirements structure/contents. |
| .rhiza/tests/structure/test_pyproject.py | Adds pyproject structure/version policy tests (including tag matching). |
| .rhiza/tests/structure/test_project_layout.py | Adds tests enforcing expected repo layout/config presence. |
| .rhiza/tests/stress/README.md | Adds documentation for stress test suite and execution options. |
| .rhiza/tests/stress/conftest.py | Adds stress test CLI options/fixtures (iterations/workers). |
| .rhiza/tests/stress/init.py | Declares stress test module. |
| .rhiza/tests/shell/test_scripts.sh | Adds shell-script validation test harness (shebang/strict mode/syntax). |
| .rhiza/tests/README.md | Adds overview documentation for the Rhiza test suite organization. |
| .rhiza/tests/integration/test_virtual_env_unexport.py | Adds integration test asserting VIRTUAL_ENV is not exported to uv. |
| .rhiza/tests/integration/test_test_mk.py | Adds integration test covering missing-tests behavior in test.mk. |
| .rhiza/tests/integration/test_docs_targets.py | Adds integration tests for book targets and MKDOCS_EXTRA_PACKAGES. |
| .rhiza/tests/integration/test_book_targets.py | Adds resilience tests for book-related Makefile targets. |
| .rhiza/tests/conftest.py | Adds root fixtures and a sandboxed git repo fixture used across .rhiza/tests. |
| .rhiza/tests/api/test_makefile_targets.py | Adds API tests validating Make targets and dry-run behavior. |
| .rhiza/tests/api/test_makefile_api.py | Adds tests verifying Makefile API extension/override behavior. |
| .rhiza/tests/api/test_make_variable_overrides.py | Adds tests for important Make variable overrides (coverage/licenses/python). |
| .rhiza/tests/api/test_github_targets.py | Adds tests ensuring GitHub-related Make targets exist and are runnable. |
| .rhiza/tests/api/conftest.py | Adds API test environment setup and split Makefile list. |
| .rhiza/template.yml | Updates Rhiza template reference from v0.18.6 to v0.18.8. |
| .rhiza/template.lock | Records the synced template SHA/ref and materialized file list. |
| .rhiza/semgrep.yml | Adds Semgrep rules (numpy security/repro/stability checks). |
| .rhiza/rhiza.mk | Adds the core Rhiza Makefile entrypoint and workflow targets/hooks. |
| .rhiza/requirements/tools.txt | Adds pinned tool requirements used by Rhiza targets/tests. |
| .rhiza/requirements/tests.txt | Adds test requirements (pytest, coverage, hypothesis, benchmark deps). |
| .rhiza/requirements/README.md | Documents the purpose and usage of requirements files. |
| .rhiza/requirements/marimo.txt | Adds Marimo dependency requirements. |
| .rhiza/requirements/docs.txt | Adds docs tooling requirements (interrogate/mike/zensical). |
| .rhiza/make.d/test.mk | Adds test/benchmark/security/docs-coverage/stress/mutation Make targets. |
| .rhiza/make.d/releasing.mk | Adds bump/release/publish Make targets with DRY_RUN support. |
| .rhiza/make.d/quality.mk | Adds fmt/deptry/license/semgrep/suppression-audit and quality meta target. |
| .rhiza/make.d/marimo.mk | Adds marimo and marimo-validate Make targets. |
| .rhiza/make.d/doctor.mk | Adds make doctor prerequisite checks with version enforcement. |
| .rhiza/make.d/custom-task.mk | Adds example custom task + post-install hook extension point. |
| .rhiza/make.d/custom-env.mk | Adds example custom environment overrides. |
| .rhiza/make.d/bootstrap.mk | Adds install/bootstrap targets including uv install and dependency sync. |
| .rhiza/make.d/book.mk | Adds book build/serve targets and reports/notebook export integration. |
| .rhiza/completions/rhiza-completion.zsh | Adds zsh completion for make targets/vars. |
| .rhiza/completions/rhiza-completion.bash | Adds bash completion for make targets/vars. |
| .rhiza/completions/README.md | Documents installing/using shell completion scripts. |
| .rhiza/.rhiza-version | Sets rhiza-tools version used by Makefile workflows. |
| .rhiza/.gitignore | Adds Rhiza-specific ignore rules (notably keeping .env). |
| .rhiza/.env | Adds Rhiza env defaults (SOURCE_FOLDER/MARIMO_FOLDER/CI matrix). |
| .rhiza/.cfg.toml | Adds bumpversion configuration used by Rhiza release tooling. |
| .python-version | Adds/updates Python version pin for toolchain/workflows. |
| .pre-commit-config.yaml | Adds pre-commit hooks for linting/formatting/security/config validation. |
| .gitignore | Replaces/expands ignore rules to match Rhiza template conventions. |
| .github/workflows/rhiza_weekly.yml | Adds weekly workflow stub delegating to Rhiza reusable workflow. |
| .github/workflows/rhiza_sync.yml | Adds sync workflow stub and schedule to keep template current. |
| .github/workflows/rhiza_release.yml | Adds full release workflow (SBOM, changelog, publish, devcontainer). |
| .github/workflows/rhiza_marimo.yml | Adds Marimo CI workflow stub. |
| .github/workflows/rhiza_codeql.yml | Adds CodeQL workflow stub. |
| .github/workflows/rhiza_ci.yml | Adds CI workflow stub. |
| .github/workflows/rhiza_book.yml | Adds docs/book workflow stub for GitHub Pages publishing. |
| .github/workflows/rhiza_benchmark.yml | Adds benchmark workflow stub. |
| .github/secret_scanning.yml | Adds secret scanning configuration and ignore paths. |
| .github/release.yml | Adds release-notes categorization configuration. |
| .github/pull_request_template.md | Adds a PR template with testing/checklist prompts. |
| .github/ISSUE_TEMPLATE/feature_request.yml | Adds feature request issue form. |
| .github/ISSUE_TEMPLATE/bug_report.yml | Adds bug report issue form. |
| .github/DISCUSSION_TEMPLATE/q-and-a.yml | Adds discussion template for Q&A. |
| .github/dependabot.yml | Adds Dependabot configuration for uv and GitHub Actions. |
| .editorconfig | Adds EditorConfig rules for consistent formatting across file types. |
| .bandit | Adds Bandit config (e.g., skip assert warnings). |
💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.
|
|
||
| 3.12 |
| line-length = 120 | ||
| # Target Python version | ||
| target-version = "py311" | ||
|
|
| custom_checkbox: true | ||
| - pymdownx.tilde | ||
| - pymdownx.snippets: | ||
| base_path: ["."] | ||
|
|
There was a problem hiding this comment.
Actionable comments posted: 15
Note
Due to the large number of review comments, Critical, Major severity comments were prioritized as inline comments.
Caution
Some comments are outside the diff and can’t be posted inline due to platform limitations.
⚠️ Outside diff range comments (1)
.rhiza/tests/api/test_make_variable_overrides.py (1)
157-162:⚠️ Potential issue | 🟡 Minor | ⚡ Quick winThis test does not verify TESTS_FOLDER usage yet.
Line 158 claims path validation, but Line 161 only checks that
pytestappears. That can pass even if Makefile ignoresTESTS_FOLDER.Proposed fix
proc = run_make(logger, ["test"]) - # The default tests folder must appear somewhere in the pytest invocation - assert "pytest" in proc.stdout + assert "pytest" in proc.stdout + assert " tests" in proc.stdout or " tests/" in proc.stdout🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the rest with a brief reason, keep changes minimal, and validate. In @.rhiza/tests/api/test_make_variable_overrides.py around lines 157 - 162, The test test_pytest_uses_tests_folder currently only asserts that "pytest" appears in proc.stdout but does not verify that the Makefile used the TESTS_FOLDER path; update the test to parse proc.stdout (from run_make) and assert that the pytest invocation includes the TESTS_FOLDER value (or a placeholder variable like TESTS_FOLDER) or the actual tests path string—e.g., locate the command string in proc.stdout and assert it contains TESTS_FOLDER (or the expected path) so the test fails if the Makefile ignores TESTS_FOLDER.
🟡 Minor comments (9)
.github/DISCUSSION_TEMPLATE/q-and-a.yml-7-7 (1)
7-7:⚠️ Potential issue | 🟡 Minor | ⚡ Quick winFix wording typo in the welcome copy.
“share how your use-case” reads incorrectly; should be “share your use case”.
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the rest with a brief reason, keep changes minimal, and validate. In @.github/DISCUSSION_TEMPLATE/q-and-a.yml at line 7, Fix the wording typo in the welcome copy string "Welcome! Use this space to ask questions, share how your use-case, or explore ideas with the community." by changing "share how your use-case" to "share your use case" so the sentence reads correctly; update the YAML value for the welcome message (the string starting with "Welcome! Use this space...") in .github/DISCUSSION_TEMPLATE/q-and-a.yml accordingly, keeping punctuation and spacing consistent..rhiza/completions/rhiza-completion.bash-31-32 (1)
31-32:⚠️ Potential issue | 🟡 Minor | ⚡ Quick winAdd missing variable completions for Bash parity.
Bash completion currently omits
COVERAGE_FAIL_UNDER=andPYTHON_VERSION=even though they’re exposed in the docs/Zsh path, so shell behavior diverges.Suggested patch
- local vars="DRY_RUN=1 BUMP=patch BUMP=minor BUMP=major ENV=dev ENV=staging ENV=prod" + local vars="DRY_RUN=1 BUMP=patch BUMP=minor BUMP=major ENV=dev ENV=staging ENV=prod COVERAGE_FAIL_UNDER= PYTHON_VERSION="🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the rest with a brief reason, keep changes minimal, and validate. In @.rhiza/completions/rhiza-completion.bash around lines 31 - 32, The Bash completion list in the vars variable (currently "DRY_RUN=1 BUMP=patch BUMP=minor BUMP=major ENV=dev ENV=staging ENV=prod") is missing the COVERAGE_FAIL_UNDER= and PYTHON_VERSION= entries; update the vars string in .rhiza/completions/rhiza-completion.bash to include "COVERAGE_FAIL_UNDER=" and "PYTHON_VERSION=" (following the same key=example pattern) so opts="$opts $vars" will expose those completions and match the Zsh/docs behavior..rhiza/tests/integration/test_book_targets.py-34-38 (1)
34-38:⚠️ Potential issue | 🟡 Minor | ⚡ Quick winAlso assert successful dry-run exit codes in these target loops.
Line 36/63 only checks one stderr phrase, so unrelated failures can still pass these tests.
Proposed fix
result = subprocess.run([MAKE, "-n", target], cwd=git_repo, capture_output=True, text=True) # nosec + assert result.returncode == 0, f"Dry-run failed for {target}: {result.stderr}" # Target should exist (not "no rule to make target") assert "no rule to make target" not in result.stderr.lower(), ( f"Target {target} should be defined in .rhiza/make.d/" )Also applies to: 61-65
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the rest with a brief reason, keep changes minimal, and validate. In @.rhiza/tests/integration/test_book_targets.py around lines 34 - 38, The test currently only checks that the stderr doesn't contain "no rule to make target" after calling subprocess.run([MAKE, "-n", target], cwd=git_repo, capture_output=True, text=True) but doesn't assert the dry-run exit code; update the assertions in both target loops (the calls to subprocess.run using MAKE and "-n") to also assert that result.returncode == 0 (or otherwise explicitly check for a successful exit code) so unrelated failures with nonzero return codes fail the test as well..rhiza/tests/README.md-80-145 (1)
80-145:⚠️ Potential issue | 🟡 Minor | ⚡ Quick winUse a single code-fence style to satisfy MD048.
Line 80, Line 87, Line 98, Line 110, Line 115, Line 120, and Line 145 use backtick fences while the repo lint rule expects tilde fences. This will keep markdownlint warnings active.
Suggested fix pattern
-```bash +~~~bash uv run pytest .rhiza/tests/ # or make test -``` +~~~🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the rest with a brief reason, keep changes minimal, and validate. In @.rhiza/tests/README.md around lines 80 - 145, Replace the backtick-style code fences (``` and ```bash) in .rhiza/tests/README.md with tilde-style fences (~~~ and ~~~bash) to satisfy MD048; specifically update each fenced block shown in the diff (the examples starting with ```bash, the category lists, stress test examples, specific test file, verbose/coverage blocks, and the Import Patterns block) so the opening and closing fences use tildes (~~~) instead of backticks, ensuring language tags (e.g., bash) are preserved.Source: Linters/SAST tools
.rhiza/tests/api/test_make_variable_overrides.py-77-85 (1)
77-85:⚠️ Potential issue | 🟡 Minor | ⚡ Quick winThis test can silently pass without validating file-based version resolution.
Line 80 gates all assertions on
python_version_file.exists(). If setup regresses and the file is missing, this test still passes.Proposed fix
python_version_file = tmp_path / ".python-version" - if python_version_file.exists(): - version = python_version_file.read_text().strip() - proc = run_make(logger, ["print-PYTHON_VERSION"], dry_run=False) - out = strip_ansi(proc.stdout) - assert version in out, f"Expected {version} in PYTHON_VERSION output; got: {out}" + assert python_version_file.exists(), "Expected setup fixture to copy .python-version" + version = python_version_file.read_text().strip() + proc = run_make(logger, ["print-PYTHON_VERSION"], dry_run=False) + out = strip_ansi(proc.stdout) + assert version in out, f"Expected {version} in PYTHON_VERSION output; got: {out}"🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the rest with a brief reason, keep changes minimal, and validate. In @.rhiza/tests/api/test_make_variable_overrides.py around lines 77 - 85, The test test_python_version_read_from_python_version_file currently skips assertions when python_version_file doesn't exist; change it to ensure deterministic behavior by either creating the .python-version file in tmp_path (write a known version string) before calling run_make, or assert python_version_file.exists() immediately so the test fails if setup is wrong; update references to python_version_file, run_make, and strip_ansi accordingly so the test always validates file-based resolution rather than silently passing..rhiza/tests/api/test_make_variable_overrides.py-141-146 (1)
141-146:⚠️ Potential issue | 🟡 Minor | ⚡ Quick winAssertion does not test the stated override behavior.
Line 142 says this checks non-overridability to empty, but Line 143 never attempts an override. This currently only checks output shape.
Proposed fix
- proc = run_make(logger, ["print-UV_NO_MODIFY_PATH"], dry_run=False) + proc = run_make( + logger, + ["print-UV_NO_MODIFY_PATH", "UV_NO_MODIFY_PATH="], + dry_run=False, + ) out = strip_ansi(proc.stdout) - assert "UV_NO_MODIFY_PATH" in out + assert "Value of UV_NO_MODIFY_PATH" in out + assert "\n1" in out🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the rest with a brief reason, keep changes minimal, and validate. In @.rhiza/tests/api/test_make_variable_overrides.py around lines 141 - 146, The test test_uv_no_modify_path_cannot_be_overridden_to_empty currently only prints UV_NO_MODIFY_PATH without attempting an override; update it to call run_make in a way that tries to override UV_NO_MODIFY_PATH to an empty string (e.g., passing an override argument that sets "UV_NO_MODIFY_PATH=") and then assert the printed output from run_make (use strip_ansi(proc.stdout)) still contains "UV_NO_MODIFY_PATH" and is not empty/was not replaced by an empty value; keep using the same helper run_make and dry_run=False to exercise the real behavior..rhiza/tests/structure/test_project_layout.py-55-57 (1)
55-57:⚠️ Potential issue | 🟡 MinorAvoid
pytest.skip()in the optional-files loop (.rhiza/tests/structure/test_project_layout.py).
pytest.skip()raises a Skip exception and stops execution of the current test on the first missing optional file, preventing checks for the rest. For missing optional files,continueinstead.Suggested patch
for filename in optional_files: if not (root / filename).exists(): - pytest.skip(f"Optional file {filename} not present in this project") + continue🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the rest with a brief reason, keep changes minimal, and validate. In @.rhiza/tests/structure/test_project_layout.py around lines 55 - 57, The loop over optional_files in test_project_layout.py incorrectly calls pytest.skip() which aborts the test on the first missing optional file; change the behavior in the loop (the block iterating optional_files) to use continue instead of pytest.skip() so the test will simply skip that filename and continue checking the remaining optional_files (locate the for filename in optional_files loop and replace pytest.skip(...) with a continue)..rhiza/tests/sync/test_readme_validation.py-60-61 (1)
60-61:⚠️ Potential issue | 🟡 Minor | ⚡ Quick winConcatenating Python blocks without separators can corrupt syntax.
Merging with an empty separator can join trailing/leading tokens across blocks.
Suggested fix
- code = "".join(code_blocks) # merged code + code = "\n\n".join(code_blocks) # merged code🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the rest with a brief reason, keep changes minimal, and validate. In @.rhiza/tests/sync/test_readme_validation.py around lines 60 - 61, The test merges code and expected result blocks using "".join which can fuse tokens across block boundaries; change the joins to include a safe separator (e.g. "\n" or "\n\n") so use code = "\n\n".join(code_blocks) (or "\n".join if single-line separation is preferred) and similarly expected = "\n\n".join(result_blocks) to preserve line breaks and avoid corrupting Python syntax in the merged strings.docs/development/TESTS.md-157-163 (1)
157-163:⚠️ Potential issue | 🟡 Minor | ⚡ Quick winAdd language identifiers to unlabeled fenced code blocks.
Line 157 and Line 250 use fenced blocks without a language, which triggers
MD040and can fail markdown linting depending on CI settings. Use explicit labels (for example,text/ini/bash) to keep docs lint-clean.Also applies to: 250-257
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the rest with a brief reason, keep changes minimal, and validate. In `@docs/development/TESTS.md` around lines 157 - 163, The fenced code blocks showing the benchmark table (the block starting with "--------------------------------------------------- benchmark: 1 tests ---------------------------------------------------") and the other unlabeled block later should be given explicit language identifiers to satisfy MD040; edit those backticks to include a label such as "text" or "ini" (e.g., change ``` to ```text) so the benchmark table and related examples are lint-clean while keeping the block contents unchanged.Source: Linters/SAST tools
🧹 Nitpick comments (1)
.rhiza/requirements/tools.txt (1)
7-7: ⚡ Quick winPin
tyto an exact version for reproducible tooling installs.Using
>=here makes local/CI behavior drift over time compared with the other pinned tool deps.Proposed change
-ty>=0.0.30 +ty==0.0.30🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the rest with a brief reason, keep changes minimal, and validate. In @.rhiza/requirements/tools.txt at line 7, Replace the loose version specifier for the tool package 'ty' with an exact pin to ensure reproducible installs: change the entry currently written as 'ty>=0.0.30' to use an exact equality pin (e.g., 'ty==0.0.30') in the tools requirements so it matches the other pinned tool dependencies.
🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.
Inline comments:
In @.bandit:
- Line 2: Remove the global "skips = B101" entry from the .bandit config and
instead scope the suppression to test code by adding inline suppressions only in
test files that intentionally use assert (e.g., add "# nosec" on the specific
assert lines in your test functions); this preserves B101 checks for runtime
code while allowing asserts in tests—search for the literal "skips = B101" to
remove it and update test files that relied on the global suppression to use "#
nosec" on the relevant assert statements.
In @.github/secret_scanning.yml:
- Around line 14-20: The current paths-ignore block is too broad (entries
docs/**/*.md and book/**) and disables secret scanning for large human-authored
content; update the paths-ignore to remove or tighten those two patterns and
replace them with narrow, synthetic-only patterns (e.g., patterns targeting
fixture/example files under docs and book such as docs/**/fixtures/**,
docs/**/*.example.* or book/**/examples/**) so that paths-ignore still contains
the original fixture skips but no longer excludes all docs or the entire book;
edit the entries under the paths-ignore key (referencing the symbols
paths-ignore, docs/**/*.md, book/**) accordingly.
In @.github/workflows/rhiza_benchmark.yml:
- Around line 17-19: The workflow's push trigger currently only lists the branch
name "main" under the push->branches key, so pushes to the repository's active
branch "master" won't trigger the benchmark; update the YAML push trigger (the
branches array under the push event) to include "master" as well (e.g., add
"master" alongside "main" or replace "main" with "master" depending on intended
behavior) so the workflow will run on pushes to the repository's active branch.
In @.github/workflows/rhiza_release.yml:
- Around line 105-110: Top-level workflow permissions currently grant write to
contents, id-token, packages, and attestations for all jobs; change the
top-level permissions to least-privilege (e.g., contents: read and minimal
defaults) and move write scopes into only the jobs that require them (the
release/publish/attestation jobs). Specifically, remove or downgrade the write
entries under the top-level permissions block and add per-job permissions
entries for the jobs that perform release/publish/attestation so that contents:
write, id-token: write, packages: write, and attestations: write appear only in
the corresponding release/publish/attestation job definitions (referencing the
job names that run release/publish/attestation).
- Line 583: The workflow exports UV_EXTRA_INDEX_URL using
secrets.uv_extra_index_url which mismatches the declared secret key
UV_EXTRA_INDEX_URL (GitHub secrets are case-sensitive); update the expression to
use secrets.UV_EXTRA_INDEX_URL (or rename the declared secret to lowercase to
match) so the export line export UV_EXTRA_INDEX_URL="${{
secrets.UV_EXTRA_INDEX_URL }}" correctly references the existing secret.
- Around line 119-588: The workflow uses mutable action tags (e.g.,
actions/checkout@v6.0.2, astral-sh/setup-uv@v7.6.0, actions/setup-python@v6.2.0,
actions/attest@v4.1.0, actions/upload-artifact@v7.0.1,
actions/download-artifact@v8.0.1, actions/attest-build-provenance@v4,
ncipollo/release-action@v1.21.0, pypa/gh-action-pypi-publish@release/v1,
devcontainers/ci@v0.3, docker/login-action@v4.2.0,
jebel-quant/rhiza/.github/actions/configure-git-auth@v0.15.2); replace each
uses: reference with the corresponding immutable commit SHA (the full 40-char
commit) for that action version to prevent retargeting. For each occurrence
(search for the unique action strings above in the workflow), update the uses
value to the same repository@<commit-sha>, verify the SHA matches the tag
version in the action’s upstream repo, and run a workflow test to ensure no
breaking changes from the pinning.
In @.github/workflows/rhiza_weekly.yml:
- Line 32: Replace the blanket "secrets: inherit" used on the reusable workflow
call with an explicit secrets mapping: identify the reusable workflow call that
uses "uses:" and replace "secrets: inherit" with only the required secrets
mapped like "MY_SECRET: ${{ secrets.MY_SECRET }}" (and any other specific
secrets needed). Ensure you remove "secrets: inherit" and list each secret
explicitly to enforce least privilege.
- Line 31: Replace the mutable tag reference in the reusable workflow invocation
(the line containing "uses:
jebel-quant/rhiza/.github/workflows/rhiza_weekly.yml@v0.18.4") with the
corresponding full commit SHA for that workflow; update the "uses:" value to end
with the full 40-character commit SHA instead of "`@v0.18.4`" (you can optionally
retain the tag as a comment for human context).
In @.rhiza/.rhiza-version:
- Line 1: The repository's Rhiza template version is out of sync (file
.rhiza/.rhiza-version contains 0.16.1 while .rhiza/template.yml and GitHub
workflow reusable actions reference v0.18.x); update .rhiza/.rhiza-version to
match the template ref series (e.g., set it to v0.18.8) and confirm that
.rhiza/template.yml and workflows (the referenced reusable workflows/actions)
consistently use the same v0.18.x tag, or alternatively add a clear
compatibility note in the repo explaining that 0.16.1 is intentionally
compatible with v0.18.8 if you choose not to change the version.
In @.rhiza/make.d/book.mk:
- Around line 31-38: The loop over notebooks using the glob "for nb in
$(MARIMO_FOLDER)/*.py" can iterate the literal pattern when no .py files exist;
modify the loop to skip non-existent matches by adding a guard such as testing
each candidate with "[ -e "$$nb" ] || continue" at the start of the loop (or
pre-check the glob with "set -- $(MARIMO_FOLDER)/*.py; [ -e "$1" ] || continue")
so the export step (the line invoking "${UV_BIN} run marimo export html ...") is
only run for real files.
In @.rhiza/make.d/bootstrap.mk:
- Line 109: Replace the unsafe force-delete command "git branch -vv | awk '/:
gone]/{print $$1}' | xargs -r git branch -D" with a safer flow: identify stale
branches (the same grep/awk can remain), but pass them to "git branch -d"
instead of "-D" and/or first filter to only merged branches using "git branch
--merged" to avoid deleting unmerged work; ensure you skip the current branch
(e.g., check "$(git rev-parse --abbrev-ref HEAD)") and only delete when the
branch is merged or confirmed, so update the invocation that currently uses
xargs git branch -D to use a safe deletion command and optional confirmation.
In @.rhiza/make.d/quality.mk:
- Around line 18-24: The nested shell if/fi sequence in the Make recipe is
missing a command separator between the inner "fi" and the outer "fi", causing a
shell parse error; change the inner "fi \\" to include a semicolon before the
backslash (i.e. "fi; \\") so the shell receives a proper separator; locate the
block that uses MARIMO_FOLDER, SOURCE_FOLDER and calls $(UVX_BIN) -p
${PYTHON_VERSION} deptry and update the inner "fi" line to "fi; \\".
In @.rhiza/tests/structure/test_pyproject.py:
- Around line 145-151: The classifiers fixture currently calls pytest.skip when
the "classifiers" key is missing which hides missing required configuration;
change the fixture (def classifiers) to assert or raise a test failure (e.g.,
use pytest.fail or a direct assert) when project.get("classifiers") is empty so
the test fails loudly instead of skipping; apply the same change to the other
fixtures in this test file that currently call pytest.skip (the second similar
fixture) so missing required sections cause test failures rather than silent
skips.
In @.rhiza/tests/sync/conftest.py:
- Around line 78-80: The test fixture is writing an outdated template ref ("ref:
v0.7.1") into the minimal .rhiza/template.yml; update the string passed to
(tmp_path / ".rhiza" / "template.yml").write_text(...) to use the current
template ref "v0.18.8" so tests validate against the correct baseline and keep
the rest of the file content unchanged.
In `@docs/index.md`:
- Line 1: The README.md snippet reference in docs/index.md won't be found
because mkdocs-base.yml restricts snippet lookup to base_path: ["."] with
docs_dir: docs; update configuration so the snippet lookup includes the
repository root or the README location (for example expand base_path to include
".." or add the root path), or move/copy the README.md into the docs/ folder and
update the reference; edit docs/mkdocs-base.yml (look for base_path and
docs_dir) to add the correct path where README.md resides so the snippet in
docs/index.md resolves during the build.
---
Outside diff comments:
In @.rhiza/tests/api/test_make_variable_overrides.py:
- Around line 157-162: The test test_pytest_uses_tests_folder currently only
asserts that "pytest" appears in proc.stdout but does not verify that the
Makefile used the TESTS_FOLDER path; update the test to parse proc.stdout (from
run_make) and assert that the pytest invocation includes the TESTS_FOLDER value
(or a placeholder variable like TESTS_FOLDER) or the actual tests path
string—e.g., locate the command string in proc.stdout and assert it contains
TESTS_FOLDER (or the expected path) so the test fails if the Makefile ignores
TESTS_FOLDER.
---
Minor comments:
In @.github/DISCUSSION_TEMPLATE/q-and-a.yml:
- Line 7: Fix the wording typo in the welcome copy string "Welcome! Use this
space to ask questions, share how your use-case, or explore ideas with the
community." by changing "share how your use-case" to "share your use case" so
the sentence reads correctly; update the YAML value for the welcome message (the
string starting with "Welcome! Use this space...") in
.github/DISCUSSION_TEMPLATE/q-and-a.yml accordingly, keeping punctuation and
spacing consistent.
In @.rhiza/completions/rhiza-completion.bash:
- Around line 31-32: The Bash completion list in the vars variable (currently
"DRY_RUN=1 BUMP=patch BUMP=minor BUMP=major ENV=dev ENV=staging ENV=prod") is
missing the COVERAGE_FAIL_UNDER= and PYTHON_VERSION= entries; update the vars
string in .rhiza/completions/rhiza-completion.bash to include
"COVERAGE_FAIL_UNDER=" and "PYTHON_VERSION=" (following the same key=example
pattern) so opts="$opts $vars" will expose those completions and match the
Zsh/docs behavior.
In @.rhiza/tests/api/test_make_variable_overrides.py:
- Around line 77-85: The test test_python_version_read_from_python_version_file
currently skips assertions when python_version_file doesn't exist; change it to
ensure deterministic behavior by either creating the .python-version file in
tmp_path (write a known version string) before calling run_make, or assert
python_version_file.exists() immediately so the test fails if setup is wrong;
update references to python_version_file, run_make, and strip_ansi accordingly
so the test always validates file-based resolution rather than silently passing.
- Around line 141-146: The test
test_uv_no_modify_path_cannot_be_overridden_to_empty currently only prints
UV_NO_MODIFY_PATH without attempting an override; update it to call run_make in
a way that tries to override UV_NO_MODIFY_PATH to an empty string (e.g., passing
an override argument that sets "UV_NO_MODIFY_PATH=") and then assert the printed
output from run_make (use strip_ansi(proc.stdout)) still contains
"UV_NO_MODIFY_PATH" and is not empty/was not replaced by an empty value; keep
using the same helper run_make and dry_run=False to exercise the real behavior.
In @.rhiza/tests/integration/test_book_targets.py:
- Around line 34-38: The test currently only checks that the stderr doesn't
contain "no rule to make target" after calling subprocess.run([MAKE, "-n",
target], cwd=git_repo, capture_output=True, text=True) but doesn't assert the
dry-run exit code; update the assertions in both target loops (the calls to
subprocess.run using MAKE and "-n") to also assert that result.returncode == 0
(or otherwise explicitly check for a successful exit code) so unrelated failures
with nonzero return codes fail the test as well.
In @.rhiza/tests/README.md:
- Around line 80-145: Replace the backtick-style code fences (``` and ```bash)
in .rhiza/tests/README.md with tilde-style fences (~~~ and ~~~bash) to satisfy
MD048; specifically update each fenced block shown in the diff (the examples
starting with ```bash, the category lists, stress test examples, specific test
file, verbose/coverage blocks, and the Import Patterns block) so the opening and
closing fences use tildes (~~~) instead of backticks, ensuring language tags
(e.g., bash) are preserved.
In @.rhiza/tests/structure/test_project_layout.py:
- Around line 55-57: The loop over optional_files in test_project_layout.py
incorrectly calls pytest.skip() which aborts the test on the first missing
optional file; change the behavior in the loop (the block iterating
optional_files) to use continue instead of pytest.skip() so the test will simply
skip that filename and continue checking the remaining optional_files (locate
the for filename in optional_files loop and replace pytest.skip(...) with a
continue).
In @.rhiza/tests/sync/test_readme_validation.py:
- Around line 60-61: The test merges code and expected result blocks using
"".join which can fuse tokens across block boundaries; change the joins to
include a safe separator (e.g. "\n" or "\n\n") so use code =
"\n\n".join(code_blocks) (or "\n".join if single-line separation is preferred)
and similarly expected = "\n\n".join(result_blocks) to preserve line breaks and
avoid corrupting Python syntax in the merged strings.
In `@docs/development/TESTS.md`:
- Around line 157-163: The fenced code blocks showing the benchmark table (the
block starting with "---------------------------------------------------
benchmark: 1 tests ---------------------------------------------------") and the
other unlabeled block later should be given explicit language identifiers to
satisfy MD040; edit those backticks to include a label such as "text" or "ini"
(e.g., change ``` to ```text) so the benchmark table and related examples are
lint-clean while keeping the block contents unchanged.
---
Nitpick comments:
In @.rhiza/requirements/tools.txt:
- Line 7: Replace the loose version specifier for the tool package 'ty' with an
exact pin to ensure reproducible installs: change the entry currently written as
'ty>=0.0.30' to use an exact equality pin (e.g., 'ty==0.0.30') in the tools
requirements so it matches the other pinned tool dependencies.
🪄 Autofix (Beta)
Fix all unresolved CodeRabbit comments on this PR:
- Push a commit to this branch (recommended)
- Create a new PR with the fixes
ℹ️ Review info
⚙️ Run configuration
Configuration used: defaults
Review profile: CHILL
Plan: Pro
Run ID: b3e7ce44-1164-4b60-be58-408c3b286fcd
⛔ Files ignored due to path filters (3)
.rhiza/assets/rhiza-logo.svgis excluded by!**/*.svg.rhiza/template.lockis excluded by!**/*.lockdocs/assets/rhiza-logo.svgis excluded by!**/*.svg
📒 Files selected for processing (76)
.bandit.editorconfig.github/DISCUSSION_TEMPLATE/q-and-a.yml.github/ISSUE_TEMPLATE/bug_report.yml.github/ISSUE_TEMPLATE/feature_request.yml.github/dependabot.yml.github/pull_request_template.md.github/release.yml.github/secret_scanning.yml.github/workflows/rhiza_benchmark.yml.github/workflows/rhiza_book.yml.github/workflows/rhiza_ci.yml.github/workflows/rhiza_codeql.yml.github/workflows/rhiza_marimo.yml.github/workflows/rhiza_release.yml.github/workflows/rhiza_sync.yml.github/workflows/rhiza_weekly.yml.gitignore.pre-commit-config.yaml.python-version.rhiza/.cfg.toml.rhiza/.env.rhiza/.gitignore.rhiza/.rhiza-version.rhiza/completions/README.md.rhiza/completions/rhiza-completion.bash.rhiza/completions/rhiza-completion.zsh.rhiza/make.d/book.mk.rhiza/make.d/bootstrap.mk.rhiza/make.d/custom-env.mk.rhiza/make.d/custom-task.mk.rhiza/make.d/doctor.mk.rhiza/make.d/marimo.mk.rhiza/make.d/quality.mk.rhiza/make.d/releasing.mk.rhiza/make.d/test.mk.rhiza/requirements/README.md.rhiza/requirements/docs.txt.rhiza/requirements/marimo.txt.rhiza/requirements/tests.txt.rhiza/requirements/tools.txt.rhiza/rhiza.mk.rhiza/semgrep.yml.rhiza/template.yml.rhiza/tests/README.md.rhiza/tests/api/conftest.py.rhiza/tests/api/test_github_targets.py.rhiza/tests/api/test_make_variable_overrides.py.rhiza/tests/api/test_makefile_api.py.rhiza/tests/api/test_makefile_targets.py.rhiza/tests/conftest.py.rhiza/tests/integration/test_book_targets.py.rhiza/tests/integration/test_docs_targets.py.rhiza/tests/integration/test_test_mk.py.rhiza/tests/integration/test_virtual_env_unexport.py.rhiza/tests/shell/test_scripts.sh.rhiza/tests/stress/README.md.rhiza/tests/stress/__init__.py.rhiza/tests/stress/conftest.py.rhiza/tests/structure/test_project_layout.py.rhiza/tests/structure/test_pyproject.py.rhiza/tests/structure/test_requirements.py.rhiza/tests/sync/conftest.py.rhiza/tests/sync/test_docstrings.py.rhiza/tests/sync/test_readme_validation.py.rhiza/tests/test_utils.py.rhiza/tests/utils/test_git_repo_fixture.py.rhiza/utils/pip_audit_policy.py.rhiza/utils/suppression_audit.pyMakefiledocs/development/MARIMO.mddocs/development/TESTS.mddocs/index.mddocs/mkdocs-base.ymlpytest.iniruff.toml
| @@ -0,0 +1,2 @@ | |||
| [bandit] | |||
| skips = B101 | |||
There was a problem hiding this comment.
Scope Bandit B101 suppression to tests instead of disabling it globally.
skips = B101 turns off assert-misuse detection repo-wide, which weakens security checks for runtime code. Keep B101 enabled globally and exclude/assert-ignore only test paths.
Suggested adjustment
[bandit]
-skips = B101
+# Keep B101 enabled globally; suppress in tests via CLI/config path excludes.
+# Example (if supported in your Bandit invocation):
+# exclude_dirs = tests,.rhiza/tests🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.
In @.bandit at line 2, Remove the global "skips = B101" entry from the .bandit
config and instead scope the suppression to test code by adding inline
suppressions only in test files that intentionally use assert (e.g., add "#
nosec" on the specific assert lines in your test functions); this preserves B101
checks for runtime code while allowing asserts in tests—search for the literal
"skips = B101" to remove it and update test files that relied on the global
suppression to use "# nosec" on the relevant assert statements.
| paths-ignore: | ||
| # Ignore test fixtures that may contain example/fake secrets | ||
| - ".rhiza/tests/**" | ||
| - "tests/**" | ||
| # Ignore documentation that references example tokens/keys | ||
| - "docs/**/*.md" | ||
| - "book/**" |
There was a problem hiding this comment.
Avoid excluding all docs and book content from secret scanning.
Line 19 and Line 20 disable scanning for large human-authored areas where accidental token pastes often happen. Keep fixture-style exclusions, but narrow docs/book ignores to specific synthetic test files only.
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.
In @.github/secret_scanning.yml around lines 14 - 20, The current paths-ignore
block is too broad (entries docs/**/*.md and book/**) and disables secret
scanning for large human-authored content; update the paths-ignore to remove or
tighten those two patterns and replace them with narrow, synthetic-only patterns
(e.g., patterns targeting fixture/example files under docs and book such as
docs/**/fixtures/**, docs/**/*.example.* or book/**/examples/**) so that
paths-ignore still contains the original fixture skips but no longer excludes
all docs or the entire book; edit the entries under the paths-ignore key
(referencing the symbols paths-ignore, docs/**/*.md, book/**) accordingly.
| push: | ||
| branches: | ||
| - main |
There was a problem hiding this comment.
Benchmark workflow trigger misses the active branch.
Line 19 only listens to main, but this repository’s PR target branch is master (June 8, 2026), so benchmark runs can be skipped on normal pushes.
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.
In @.github/workflows/rhiza_benchmark.yml around lines 17 - 19, The workflow's
push trigger currently only lists the branch name "main" under the
push->branches key, so pushes to the repository's active branch "master" won't
trigger the benchmark; update the YAML push trigger (the branches array under
the push event) to include "master" as well (e.g., add "master" alongside "main"
or replace "main" with "master" depending on intended behavior) so the workflow
will run on pushes to the repository's active branch.
| permissions: | ||
| contents: write # Needed to create releases | ||
| id-token: write # Needed for OIDC authentication with PyPI | ||
| packages: write # Needed to publish devcontainer image | ||
| attestations: write # Needed for SLSA provenance attestations (public repos only) | ||
|
|
There was a problem hiding this comment.
Reduce workflow-level write permissions and grant write only per job.
Lines 106-109 currently give write scopes to every job. Move these to only jobs that truly need them (release/publish/attestation steps) to enforce least privilege.
🧰 Tools
🪛 zizmor (1.25.2)
[error] 106-106: overly broad permissions (excessive-permissions): contents: write is overly broad at the workflow level
(excessive-permissions)
[error] 107-107: overly broad permissions (excessive-permissions): id-token: write is overly broad at the workflow level
(excessive-permissions)
[error] 108-108: overly broad permissions (excessive-permissions): packages: write is overly broad at the workflow level
(excessive-permissions)
[error] 109-109: overly broad permissions (excessive-permissions): attestations: write is overly broad at the workflow level
(excessive-permissions)
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.
In @.github/workflows/rhiza_release.yml around lines 105 - 110, Top-level
workflow permissions currently grant write to contents, id-token, packages, and
attestations for all jobs; change the top-level permissions to least-privilege
(e.g., contents: read and minimal defaults) and move write scopes into only the
jobs that require them (the release/publish/attestation jobs). Specifically,
remove or downgrade the write entries under the top-level permissions block and
add per-job permissions entries for the jobs that perform
release/publish/attestation so that contents: write, id-token: write, packages:
write, and attestations: write appear only in the corresponding
release/publish/attestation job definitions (referencing the job names that run
release/publish/attestation).
Source: Linters/SAST tools
| uses: actions/checkout@v6.0.2 | ||
| with: | ||
| fetch-depth: 0 | ||
|
|
||
| - name: Set Tag Variable | ||
| id: set_tag | ||
| run: | | ||
| if [[ -n "${{ inputs.tag }}" ]]; then | ||
| TAG="${{ inputs.tag }}" | ||
| else | ||
| TAG="${GITHUB_REF#refs/tags/}" | ||
| fi | ||
| echo "tag=$TAG" >> "$GITHUB_OUTPUT" | ||
|
|
||
| - name: Validate Release | ||
| env: | ||
| GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} | ||
| TAG: ${{ steps.set_tag.outputs.tag }} | ||
| run: | | ||
| if gh release view "$TAG" >/dev/null 2>&1; then | ||
| DRAFT_STATUS=$(gh release view "$TAG" --json isDraft --jq '.isDraft') | ||
| if [ "$DRAFT_STATUS" != "true" ]; then | ||
| echo "::error::Release '$TAG' already exists and is not a draft. Please use a new tag." | ||
| exit 1 | ||
| else | ||
| echo "::warning::Release '$TAG' exists as a draft. Will proceed to update it." | ||
| fi | ||
| fi | ||
|
|
||
| - name: Install uv | ||
| uses: astral-sh/setup-uv@v7.6.0 | ||
|
|
||
| - name: Ensure release version is newer than latest published | ||
| env: | ||
| TAG: ${{ steps.set_tag.outputs.tag }} | ||
| run: | | ||
| # Backstop for issue #1126: refuse a tag that is not strictly newer than the | ||
| # highest version already released. A diverged branch with a stale pyproject.toml | ||
| # can otherwise publish an older version (e.g. v0.3.3 after v0.4.0). The bump | ||
| # tooling enforces the same rule; this guards manually-pushed tags too. | ||
| git tag -l 'v*' | uv run --with packaging --no-project python3 -c ' | ||
| import sys | ||
| from packaging.version import Version, InvalidVersion | ||
|
|
||
| new = sys.argv[1].lstrip("v") | ||
| try: | ||
| new_v = Version(new) | ||
| except InvalidVersion: | ||
| print(f"::error::Tag {sys.argv[1]} is not a valid version") | ||
| sys.exit(1) | ||
|
|
||
| latest = None | ||
| for line in sys.stdin: | ||
| tag = line.strip().lstrip("v") | ||
| if not tag: | ||
| continue | ||
| try: | ||
| candidate = Version(tag) | ||
| except InvalidVersion: | ||
| continue | ||
| if candidate == new_v: | ||
| continue | ||
| if latest is None or candidate > latest: | ||
| latest = candidate | ||
|
|
||
| if latest is not None and new_v <= latest: | ||
| print( | ||
| f"::error::Refusing to release v{new_v}: it is not newer than the latest " | ||
| f"released version v{latest} (issue #1126). Sync with the default branch and bump again." | ||
| ) | ||
| sys.exit(1) | ||
| print(f"v{new_v} is newer than the latest released version v{latest}" if latest | ||
| else f"v{new_v} is the first release") | ||
| ' "$TAG" | ||
|
|
||
| build: | ||
| name: Build | ||
| runs-on: ubuntu-latest | ||
| needs: tag | ||
| steps: | ||
| - name: Checkout Code | ||
| uses: actions/checkout@v6.0.2 | ||
| with: | ||
| fetch-depth: 0 | ||
|
|
||
| - name: Install uv | ||
| uses: astral-sh/setup-uv@v7.6.0 | ||
| with: | ||
| version: "0.11.16" | ||
|
|
||
| - name: Configure git auth for private packages | ||
| uses: jebel-quant/rhiza/.github/actions/configure-git-auth@v0.15.2 | ||
| with: | ||
| token: ${{ secrets.GH_PAT }} | ||
|
|
||
| - name: Verify version matches tag | ||
| if: hashFiles('pyproject.toml') != '' | ||
| run: | | ||
| TAG_VERSION="${{ needs.tag.outputs.tag }}" | ||
| TAG_VERSION=${TAG_VERSION#v} | ||
| PROJECT_VERSION=$(uv version --short) | ||
|
|
||
| # Normalize tag version to PEP 440 format for comparison. | ||
| # Tags use semver format (e.g., 0.11.1-beta.1) while uv version --short | ||
| # returns PEP 440 normalized format (e.g., 0.11.1b1). | ||
| NORMALIZED_TAG=$(uv run --with packaging --no-project python3 -c "from packaging.version import Version; print(Version('$TAG_VERSION'))") | ||
|
|
||
| if [[ "$PROJECT_VERSION" != "$NORMALIZED_TAG" ]]; then | ||
| echo "::error::Version mismatch: pyproject.toml has '$PROJECT_VERSION' but tag is '$NORMALIZED_TAG' (from tag '$TAG_VERSION')" | ||
| exit 1 | ||
| fi | ||
| echo "Version verified: $PROJECT_VERSION matches tag (normalized: $NORMALIZED_TAG)" | ||
|
|
||
| - name: Detect buildable Python package | ||
| id: buildable | ||
| run: | | ||
| if [[ -f pyproject.toml ]] && grep -q '^\[build-system\]' pyproject.toml; then | ||
| echo "buildable=true" >> "$GITHUB_OUTPUT" | ||
| else | ||
| echo "buildable=false" >> "$GITHUB_OUTPUT" | ||
| fi | ||
|
|
||
| - name: Build | ||
| if: steps.buildable.outputs.buildable == 'true' | ||
| run: | | ||
| printf "[INFO] Building package...\n" | ||
| uv build | ||
|
|
||
| - name: Install Python for SBOM generation | ||
| if: hashFiles('pyproject.toml') != '' | ||
| uses: actions/setup-python@v6.2.0 | ||
| with: | ||
| python-version-file: .python-version | ||
|
|
||
| - name: Sync environment for SBOM generation | ||
| if: hashFiles('pyproject.toml') != '' | ||
| run: | | ||
| export UV_EXTRA_INDEX_URL="${{ secrets.UV_EXTRA_INDEX_URL }}" | ||
| uv sync --all-extras --all-groups --frozen | ||
|
|
||
| - name: Generate SBOM (CycloneDX) | ||
| if: hashFiles('pyproject.toml') != '' | ||
| run: | | ||
| printf "[INFO] Generating SBOM in CycloneDX format...\n" | ||
| # Note: uvx caches the tool environment, so the second call is fast | ||
| uvx --from 'cyclonedx-bom>=7.0.0' cyclonedx-py environment --pyproject pyproject.toml --of JSON -o sbom.cdx.json | ||
| uvx --from 'cyclonedx-bom>=7.0.0' cyclonedx-py environment --pyproject pyproject.toml --of XML -o sbom.cdx.xml | ||
| printf "[INFO] SBOM generation complete\n" | ||
| printf "Generated files:\n" | ||
| ls -lh sbom.cdx.* | ||
|
|
||
| - name: Attest SBOM | ||
| # Attest only the JSON format as it's the canonical machine-readable format. | ||
| # The XML format is provided for compatibility but doesn't need separate attestation. | ||
| if: hashFiles('pyproject.toml') != '' && github.event.repository.private == false | ||
| uses: actions/attest@v4.1.0 | ||
| with: | ||
| subject-path: sbom.cdx.json | ||
| sbom-path: sbom.cdx.json | ||
|
|
||
| - name: Upload SBOM artifacts | ||
| if: hashFiles('pyproject.toml') != '' | ||
| uses: actions/upload-artifact@v7.0.1 | ||
| with: | ||
| name: sbom | ||
| path: | | ||
| sbom.cdx.json | ||
| sbom.cdx.xml | ||
|
|
||
| - name: Generate SLSA provenance attestations | ||
| if: steps.buildable.outputs.buildable == 'true' && github.event.repository.private == false | ||
| uses: actions/attest-build-provenance@v4 | ||
| with: | ||
| subject-path: dist/* | ||
|
|
||
| - name: Upload dist artifact | ||
| if: steps.buildable.outputs.buildable == 'true' | ||
| uses: actions/upload-artifact@v7.0.1 | ||
| with: | ||
| name: dist | ||
| path: dist | ||
|
|
||
| # Don't try to upload artifacts to GitHub Release at all | ||
| draft-release: | ||
| name: Draft GitHub Release | ||
| runs-on: ubuntu-latest | ||
| needs: [tag, build] | ||
|
|
||
| steps: | ||
| - name: Checkout Code | ||
| uses: actions/checkout@v6.0.2 | ||
| with: | ||
| fetch-depth: 0 | ||
|
|
||
| - name: Install uv | ||
| uses: astral-sh/setup-uv@v7.6.0 | ||
|
|
||
| - name: Generate release notes with git-cliff | ||
| run: uvx git-cliff --latest --output RELEASE_NOTES.md | ||
|
|
||
| - name: Download SBOM artifact | ||
| # Downloads sbom.cdx.json and sbom.cdx.xml into sbom/ directory | ||
| uses: actions/download-artifact@v8.0.1 | ||
| with: | ||
| name: sbom | ||
| path: sbom | ||
| continue-on-error: true | ||
|
|
||
| - name: Create GitHub Release with artifacts | ||
| uses: ncipollo/release-action@v1.21.0 | ||
| with: | ||
| tag: ${{ needs.tag.outputs.tag }} | ||
| name: ${{ needs.tag.outputs.tag }} | ||
| bodyFile: RELEASE_NOTES.md | ||
| draft: true | ||
| allowUpdates: true | ||
| artifacts: "sbom/*" | ||
|
|
||
| update-changelog: | ||
| name: Update CHANGELOG.md | ||
| runs-on: ubuntu-latest | ||
| needs: [tag, draft-release] | ||
| steps: | ||
| - name: Checkout Default Branch | ||
| uses: actions/checkout@v6.0.2 | ||
| with: | ||
| ref: ${{ github.event.repository.default_branch }} | ||
| fetch-depth: 0 | ||
| token: ${{ secrets.GITHUB_TOKEN }} | ||
|
|
||
| - name: Install uv | ||
| uses: astral-sh/setup-uv@v7.6.0 | ||
|
|
||
| - name: Generate CHANGELOG.md with git-cliff | ||
| run: uvx git-cliff --output CHANGELOG.md | ||
|
|
||
| - name: Commit and push CHANGELOG.md | ||
| env: | ||
| TAG: ${{ needs.tag.outputs.tag }} | ||
| run: | | ||
| git config user.name "github-actions[bot]" | ||
| git config user.email "41898282+github-actions[bot]@users.noreply.github.com" | ||
| git add CHANGELOG.md | ||
| git diff --staged --quiet || git commit -m "chore: update CHANGELOG.md for $TAG [skip ci]" | ||
| git push origin ${{ github.event.repository.default_branch }} | ||
|
|
||
| # Decide at step-level whether to publish | ||
| pypi: | ||
| name: Publish to PyPI | ||
| runs-on: ubuntu-latest | ||
| environment: release | ||
| needs: [tag, build, draft-release] | ||
| outputs: | ||
| should_publish: ${{ steps.check_dist.outputs.should_publish }} | ||
|
|
||
| steps: | ||
| - name: Checkout Code | ||
| uses: actions/checkout@v6.0.2 | ||
| with: | ||
| fetch-depth: 0 | ||
|
|
||
| - name: Download dist artifact | ||
| uses: actions/download-artifact@v8.0.1 | ||
| with: | ||
| name: dist | ||
| path: dist | ||
| # Continue if dist folder is not found. Don't fail the job. | ||
| continue-on-error: true | ||
|
|
||
| - name: Check if dist contains artifacts and not marked as private | ||
| id: check_dist | ||
| run: | | ||
| if [[ ! -d dist ]]; then | ||
| echo "::warning::No folder dist/. Skipping PyPI publish." | ||
| echo "should_publish=false" >> "$GITHUB_OUTPUT" | ||
| else | ||
| if grep -R "Private :: Do Not Upload" pyproject.toml; then | ||
| echo "should_publish=false" >> "$GITHUB_OUTPUT" | ||
| else | ||
| echo "should_publish=true" >> "$GITHUB_OUTPUT" | ||
| fi | ||
| fi | ||
| cat "$GITHUB_OUTPUT" | ||
|
|
||
| # this should not take place, as "Private :: Do Not Upload" set in pyproject.toml | ||
| # repository-url and password only used for custom feeds, not for PyPI with OIDC | ||
| - name: Publish to PyPI | ||
| if: ${{ steps.check_dist.outputs.should_publish == 'true' }} | ||
| uses: pypa/gh-action-pypi-publish@release/v1 | ||
| with: | ||
| packages-dir: dist/ | ||
| skip-existing: true | ||
| repository-url: ${{ vars.PYPI_REPOSITORY_URL }} | ||
| password: ${{ secrets.PYPI_TOKEN }} | ||
|
|
||
|
|
||
| conda: | ||
| name: Generate Conda Recipe | ||
| runs-on: ubuntu-latest | ||
| needs: [tag, pypi] | ||
| outputs: | ||
| should_generate: ${{ steps.check_conda.outputs.should_generate }} | ||
|
|
||
| steps: | ||
| - name: Checkout Code | ||
| uses: actions/checkout@v6.0.2 | ||
| with: | ||
| fetch-depth: 0 | ||
|
|
||
| - name: Check if conda recipe should be generated | ||
| id: check_conda | ||
| env: | ||
| PUBLISH_CONDA: ${{ vars.PUBLISH_CONDA }} | ||
| run: | | ||
| PUBLISH_CONDA="${PUBLISH_CONDA:-true}" | ||
| if [[ "$PUBLISH_CONDA" == "true" ]] && [[ "${{ needs.pypi.outputs.should_publish }}" == "true" ]] && [[ "${{ needs.pypi.result }}" == "success" ]]; then | ||
| echo "should_generate=true" >> "$GITHUB_OUTPUT" | ||
| else | ||
| echo "should_generate=false" >> "$GITHUB_OUTPUT" | ||
| echo "⏭️ Skipping conda recipe generation (PUBLISH_CONDA not true, or PyPI publish disabled or failed)" | ||
| fi | ||
|
|
||
| - name: Set up Python | ||
| if: steps.check_conda.outputs.should_generate == 'true' | ||
| uses: actions/setup-python@v6.2.0 | ||
| with: | ||
| python-version-file: .python-version | ||
|
|
||
| - name: Install grayskull | ||
| if: steps.check_conda.outputs.should_generate == 'true' | ||
| run: python -m pip install --upgrade grayskull | ||
|
|
||
| - name: Generate conda recipe with grayskull | ||
| if: steps.check_conda.outputs.should_generate == 'true' | ||
| run: | | ||
| PACKAGE_NAME=$(python -c "import tomllib; print(tomllib.load(open('pyproject.toml', 'rb'))['project']['name'])") | ||
| mkdir -p /tmp/conda-recipe | ||
| cd /tmp/conda-recipe | ||
|
|
||
| grayskull pypi "$PACKAGE_NAME" --strict-conda-forge | ||
|
|
||
| RECIPE_PATH=$(find . -type f -path "*/meta.yaml" | head -n 1) | ||
| if [[ -z "$RECIPE_PATH" ]]; then | ||
| echo "::error::grayskull did not produce a meta.yaml file" | ||
| exit 1 | ||
| fi | ||
|
|
||
| mkdir -p "$GITHUB_WORKSPACE/conda-recipe" | ||
| cp "$RECIPE_PATH" "$GITHUB_WORKSPACE/conda-recipe/meta.yaml" | ||
|
|
||
| - name: Upload conda recipe artifact | ||
| if: steps.check_conda.outputs.should_generate == 'true' | ||
| uses: actions/upload-artifact@v7.0.1 | ||
| with: | ||
| name: conda-recipe | ||
| path: conda-recipe/meta.yaml | ||
|
|
||
| devcontainer: | ||
| name: Publish Devcontainer Image | ||
| runs-on: ubuntu-latest | ||
| environment: release | ||
| needs: [tag, build, draft-release] | ||
| outputs: | ||
| should_publish: ${{ steps.check_publish.outputs.should_publish }} | ||
| image_name: ${{ steps.image_name.outputs.image_name }} | ||
| steps: | ||
| - name: Checkout Code | ||
| uses: actions/checkout@v6.0.2 | ||
| with: | ||
| fetch-depth: 0 | ||
|
|
||
| - name: Check if devcontainer should be published | ||
| id: check_publish | ||
| env: | ||
| PUBLISH_DEVCONTAINER: ${{ vars.PUBLISH_DEVCONTAINER }} | ||
| run: | | ||
| if [[ "$PUBLISH_DEVCONTAINER" == "true" ]] && [[ -d ".devcontainer" ]]; then | ||
| echo "should_publish=true" >> "$GITHUB_OUTPUT" | ||
| echo "🚀 Will build and publish devcontainer image" | ||
| else | ||
| echo "should_publish=false" >> "$GITHUB_OUTPUT" | ||
| echo "⏭️ Skipping devcontainer publish (PUBLISH_DEVCONTAINER not true or .devcontainer missing)" | ||
| fi | ||
|
|
||
| - name: Set registry | ||
| if: steps.check_publish.outputs.should_publish == 'true' | ||
| id: registry | ||
| run: | | ||
| REGISTRY="${{ vars.DEVCONTAINER_REGISTRY }}" | ||
| if [ -z "$REGISTRY" ]; then | ||
| REGISTRY="ghcr.io" | ||
| fi | ||
| echo "registry=$REGISTRY" >> "$GITHUB_OUTPUT" | ||
|
|
||
| - name: Login to Container Registry | ||
| if: steps.check_publish.outputs.should_publish == 'true' | ||
| uses: docker/login-action@v4.2.0 | ||
| with: | ||
| registry: ${{ steps.registry.outputs.registry }} | ||
| username: ${{ github.repository_owner }} | ||
| password: ${{ secrets.GITHUB_TOKEN }} | ||
|
|
||
| - name: Get lowercase repository owner | ||
| if: steps.check_publish.outputs.should_publish == 'true' | ||
| id: repo_owner | ||
| run: echo "owner_lc=$(echo '${{ github.repository_owner }}' | tr '[:upper:]' '[:lower:]')" >> "$GITHUB_OUTPUT" | ||
|
|
||
| - name: Get Image Name | ||
| if: steps.check_publish.outputs.should_publish == 'true' | ||
| id: image_name | ||
| run: | | ||
| # Check if custom name is provided, otherwise use default | ||
| if [ -z "${{ vars.DEVCONTAINER_IMAGE_NAME }}" ]; then | ||
| REPO_NAME_LC=$(echo "${{ github.event.repository.name }}" | tr '[:upper:]' '[:lower:]') | ||
| # Sanitize repo name: replace invalid characters with hyphens | ||
| # Docker image names must match [a-z0-9]+([._-][a-z0-9]+)* | ||
| # Replace leading dots and multiple consecutive separators | ||
| REPO_NAME_SANITIZED=$(echo "$REPO_NAME_LC" | sed 's/^[._-]*//; s/[._-][._-]*/-/g') | ||
| IMAGE_NAME="$REPO_NAME_SANITIZED/devcontainer" | ||
| echo "Using default image name component: $IMAGE_NAME" | ||
| else | ||
| IMAGE_NAME="${{ vars.DEVCONTAINER_IMAGE_NAME }}" | ||
| echo "Using custom image name component: $IMAGE_NAME" | ||
| fi | ||
|
|
||
| # Validate the image component matches [a-z0-9]+([._-][a-z0-9]+)* with optional / separators | ||
| if ! echo "$IMAGE_NAME" | grep -qE '^[a-z0-9]+([._-][a-z0-9]+)*(/[a-z0-9]+([._-][a-z0-9]+)*)*$'; then | ||
| echo "::error::Invalid image name component: $IMAGE_NAME" | ||
| echo "::error::Each component must match [a-z0-9]+([._-][a-z0-9]+)* separated by /" | ||
| exit 1 | ||
| fi | ||
|
|
||
| IMAGE_NAME="${{ steps.registry.outputs.registry }}/${{ steps.repo_owner.outputs.owner_lc }}/$IMAGE_NAME" | ||
| echo "✅ Final image name: $IMAGE_NAME" | ||
| echo "image_name=$IMAGE_NAME" >> "$GITHUB_OUTPUT" | ||
|
|
||
| - name: Build and Publish Devcontainer Image | ||
| if: steps.check_publish.outputs.should_publish == 'true' | ||
| uses: devcontainers/ci@v0.3 | ||
| with: | ||
| configFile: .devcontainer/devcontainer.json | ||
| push: always | ||
| imageName: ${{ steps.image_name.outputs.image_name }} | ||
| imageTag: ${{ needs.tag.outputs.tag }} | ||
|
|
||
| finalise-release: | ||
| name: Finalise Release | ||
| runs-on: ubuntu-latest | ||
| needs: [tag, pypi, conda, devcontainer] | ||
| if: needs.pypi.result == 'success' || needs.conda.result == 'success' || needs.devcontainer.result == 'success' | ||
| steps: | ||
| - name: Checkout Code | ||
| uses: actions/checkout@v6.0.2 | ||
| with: | ||
| fetch-depth: 0 | ||
|
|
||
| - name: Install uv | ||
| uses: astral-sh/setup-uv@v7.6.0 | ||
| with: | ||
| version: "0.11.16" | ||
|
|
||
| - name: "Sync the virtual environment for ${{ github.repository }}" | ||
| shell: bash | ||
| run: | | ||
| export UV_EXTRA_INDEX_URL="${{ secrets.uv_extra_index_url }}" | ||
| # will just use .python-version? | ||
| uv sync --all-extras --all-groups --frozen | ||
|
|
||
| - name: Set up Python | ||
| uses: actions/setup-python@v6.2.0 |
There was a problem hiding this comment.
🧩 Analysis chain
🏁 Script executed:
#!/bin/bash
# List non-SHA action references in this workflow.
rg -nP '^\s*uses:\s*[^@]+@([A-Za-z0-9._/\-]+)$' .github/workflows/rhiza_release.yml \
| rg -v '@[0-9a-f]{40}$'Repository: tschm/pycharting
Length of output: 1935
Pin GitHub Actions to full commit SHAs in .github/workflows/rhiza_release.yml
Release pipeline security policy: this workflow uses mutable action tags (retargetable). Pin (at least) these actions to immutable commit SHAs instead of tags: actions/checkout@v6.0.2, astral-sh/setup-uv@v7.6.0, actions/setup-python@v6.2.0, actions/attest@v4.1.0, actions/upload-artifact@v7.0.1, actions/download-artifact@v8.0.1, actions/attest-build-provenance@v4, ncipollo/release-action@v1.21.0, pypa/gh-action-pypi-publish@release/v1, devcontainers/ci@v0.3, docker/login-action@v4.2.0, and jebel-quant/rhiza/.github/actions/configure-git-auth@v0.15.2.
🧰 Tools
🪛 zizmor (1.25.2)
[warning] 199-202: credential persistence through GitHub Actions artifacts (artipacked): does not set persist-credentials: false
(artipacked)
[warning] 308-311: credential persistence through GitHub Actions artifacts (artipacked): does not set persist-credentials: false
(artipacked)
[warning] 342-347: credential persistence through GitHub Actions artifacts (artipacked): does not set persist-credentials: false
(artipacked)
[warning] 375-378: credential persistence through GitHub Actions artifacts (artipacked): does not set persist-credentials: false
(artipacked)
[warning] 423-426: credential persistence through GitHub Actions artifacts (artipacked): does not set persist-credentials: false
(artipacked)
[warning] 485-488: credential persistence through GitHub Actions artifacts (artipacked): does not set persist-credentials: false
(artipacked)
[warning] 570-573: credential persistence through GitHub Actions artifacts (artipacked): does not set persist-credentials: false
(artipacked)
[error] 126-126: code injection via template expansion (template-injection): may expand into attacker-controllable code
(template-injection)
[error] 127-127: code injection via template expansion (template-injection): may expand into attacker-controllable code
(template-injection)
[info] 217-217: code injection via template expansion (template-injection): may expand into attacker-controllable code
(template-injection)
[error] 363-363: code injection via template expansion (template-injection): may expand into attacker-controllable code
(template-injection)
[info] 434-434: code injection via template expansion (template-injection): may expand into attacker-controllable code
(template-injection)
[info] 507-507: code injection via template expansion (template-injection): may expand into attacker-controllable code
(template-injection)
[info] 531-531: code injection via template expansion (template-injection): may expand into attacker-controllable code
(template-injection)
[error] 532-532: code injection via template expansion (template-injection): may expand into attacker-controllable code
(template-injection)
[info] 540-540: code injection via template expansion (template-injection): may expand into attacker-controllable code
(template-injection)
[info] 551-551: code injection via template expansion (template-injection): may expand into attacker-controllable code
(template-injection)
[info] 551-551: code injection via template expansion (template-injection): may expand into attacker-controllable code
(template-injection)
[error] 119-119: unpinned action reference (unpinned-uses): action is not pinned to a hash (required by blanket policy)
(unpinned-uses)
[error] 149-149: unpinned action reference (unpinned-uses): action is not pinned to a hash (required by blanket policy)
(unpinned-uses)
[error] 200-200: unpinned action reference (unpinned-uses): action is not pinned to a hash (required by blanket policy)
(unpinned-uses)
[error] 205-205: unpinned action reference (unpinned-uses): action is not pinned to a hash (required by blanket policy)
(unpinned-uses)
[error] 210-210: unpinned action reference (unpinned-uses): action is not pinned to a hash (required by blanket policy)
(unpinned-uses)
[error] 249-249: unpinned action reference (unpinned-uses): action is not pinned to a hash (required by blanket policy)
(unpinned-uses)
[error] 274-274: unpinned action reference (unpinned-uses): action is not pinned to a hash (required by blanket policy)
(unpinned-uses)
[error] 281-281: unpinned action reference (unpinned-uses): action is not pinned to a hash (required by blanket policy)
(unpinned-uses)
[error] 290-290: unpinned action reference (unpinned-uses): action is not pinned to a hash (required by blanket policy)
(unpinned-uses)
[error] 296-296: unpinned action reference (unpinned-uses): action is not pinned to a hash (required by blanket policy)
(unpinned-uses)
[error] 309-309: unpinned action reference (unpinned-uses): action is not pinned to a hash (required by blanket policy)
(unpinned-uses)
[error] 314-314: unpinned action reference (unpinned-uses): action is not pinned to a hash (required by blanket policy)
(unpinned-uses)
[error] 321-321: unpinned action reference (unpinned-uses): action is not pinned to a hash (required by blanket policy)
(unpinned-uses)
[error] 328-328: unpinned action reference (unpinned-uses): action is not pinned to a hash (required by blanket policy)
(unpinned-uses)
[error] 343-343: unpinned action reference (unpinned-uses): action is not pinned to a hash (required by blanket policy)
(unpinned-uses)
[error] 350-350: unpinned action reference (unpinned-uses): action is not pinned to a hash (required by blanket policy)
(unpinned-uses)
[error] 376-376: unpinned action reference (unpinned-uses): action is not pinned to a hash (required by blanket policy)
(unpinned-uses)
[error] 381-381: unpinned action reference (unpinned-uses): action is not pinned to a hash (required by blanket policy)
(unpinned-uses)
[error] 407-407: unpinned action reference (unpinned-uses): action is not pinned to a hash (required by blanket policy)
(unpinned-uses)
[error] 424-424: unpinned action reference (unpinned-uses): action is not pinned to a hash (required by blanket policy)
(unpinned-uses)
[error] 443-443: unpinned action reference (unpinned-uses): action is not pinned to a hash (required by blanket policy)
(unpinned-uses)
[error] 471-471: unpinned action reference (unpinned-uses): action is not pinned to a hash (required by blanket policy)
(unpinned-uses)
[error] 486-486: unpinned action reference (unpinned-uses): action is not pinned to a hash (required by blanket policy)
(unpinned-uses)
[error] 515-515: unpinned action reference (unpinned-uses): action is not pinned to a hash (required by blanket policy)
(unpinned-uses)
[error] 557-557: unpinned action reference (unpinned-uses): action is not pinned to a hash (required by blanket policy)
(unpinned-uses)
[error] 571-571: unpinned action reference (unpinned-uses): action is not pinned to a hash (required by blanket policy)
(unpinned-uses)
[error] 576-576: unpinned action reference (unpinned-uses): action is not pinned to a hash (required by blanket policy)
(unpinned-uses)
[error] 588-588: unpinned action reference (unpinned-uses): action is not pinned to a hash (required by blanket policy)
(unpinned-uses)
[error] 149-149: runtime artifacts potentially vulnerable to a cache poisoning attack (cache-poisoning): enables caching by default
(cache-poisoning)
[error] 205-205: runtime artifacts potentially vulnerable to a cache poisoning attack (cache-poisoning): enables caching by default
(cache-poisoning)
[error] 314-314: runtime artifacts potentially vulnerable to a cache poisoning attack (cache-poisoning): enables caching by default
(cache-poisoning)
[error] 350-350: runtime artifacts potentially vulnerable to a cache poisoning attack (cache-poisoning): enables caching by default
(cache-poisoning)
[error] 576-576: runtime artifacts potentially vulnerable to a cache poisoning attack (cache-poisoning): enables caching by default
(cache-poisoning)
[info] 328-328: action functionality is already included by the runner (superfluous-actions): use gh release in a script step
(superfluous-actions)
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.
In @.github/workflows/rhiza_release.yml around lines 119 - 588, The workflow
uses mutable action tags (e.g., actions/checkout@v6.0.2,
astral-sh/setup-uv@v7.6.0, actions/setup-python@v6.2.0, actions/attest@v4.1.0,
actions/upload-artifact@v7.0.1, actions/download-artifact@v8.0.1,
actions/attest-build-provenance@v4, ncipollo/release-action@v1.21.0,
pypa/gh-action-pypi-publish@release/v1, devcontainers/ci@v0.3,
docker/login-action@v4.2.0,
jebel-quant/rhiza/.github/actions/configure-git-auth@v0.15.2); replace each
uses: reference with the corresponding immutable commit SHA (the full 40-char
commit) for that action version to prevent retargeting. For each occurrence
(search for the unique action strings above in the workflow), update the uses
value to the same repository@<commit-sha>, verify the SHA matches the tag
version in the action’s upstream repo, and run a workflow test to ensure no
breaking changes from the pinning.
Source: Linters/SAST tools
|
|
||
| @git fetch --prune | ||
|
|
||
| @git branch -vv | awk '/: gone]/{print $$1}' | xargs -r git branch -D |
There was a problem hiding this comment.
Avoid force-deleting stale branches in clean.
Using git branch -D can remove unmerged local commits and cause irreversible local data loss for developers running a routine cleanup.
Suggested patch
- `@git` branch -vv | awk '/: gone]/{print $$1}' | xargs -r git branch -D
+ `@git` branch -vv | awk '/: gone]/{print $$1}' | sed 's/^\*//' | awk 'NF' | xargs -r git branch -d📝 Committable suggestion
‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.
| @git branch -vv | awk '/: gone]/{print $$1}' | xargs -r git branch -D | |
| `@git` branch -vv | awk '/: gone]/{print $$1}' | sed 's/^\*//' | awk 'NF' | xargs -r git branch -d |
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.
In @.rhiza/make.d/bootstrap.mk at line 109, Replace the unsafe force-delete
command "git branch -vv | awk '/: gone]/{print $$1}' | xargs -r git branch -D"
with a safer flow: identify stale branches (the same grep/awk can remain), but
pass them to "git branch -d" instead of "-D" and/or first filter to only merged
branches using "git branch --merged" to avoid deleting unmerged work; ensure you
skip the current branch (e.g., check "$(git rev-parse --abbrev-ref HEAD)") and
only delete when the branch is merged or confirmed, so update the invocation
that currently uses xargs git branch -D to use a safe deletion command and
optional confirmation.
| @if [ -d ${MARIMO_FOLDER} ]; then \ | ||
| if [ -d ${SOURCE_FOLDER} ]; then \ | ||
| $(UVX_BIN) -p ${PYTHON_VERSION} deptry ${MARIMO_FOLDER} ${SOURCE_FOLDER} --ignore DEP004; \ | ||
| else \ | ||
| $(UVX_BIN) -p ${PYTHON_VERSION} deptry ${MARIMO_FOLDER} --ignore DEP004; \ | ||
| fi \ | ||
| fi |
There was a problem hiding this comment.
Fix missing command separator in nested if block.
Line 23 ends the inner block as fi \ and then immediately continues to the outer fi, yielding a shell parse error (fi fi) when this recipe is evaluated.
Proposed fix
- fi \
+ fi; \
fi📝 Committable suggestion
‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.
| @if [ -d ${MARIMO_FOLDER} ]; then \ | |
| if [ -d ${SOURCE_FOLDER} ]; then \ | |
| $(UVX_BIN) -p ${PYTHON_VERSION} deptry ${MARIMO_FOLDER} ${SOURCE_FOLDER} --ignore DEP004; \ | |
| else \ | |
| $(UVX_BIN) -p ${PYTHON_VERSION} deptry ${MARIMO_FOLDER} --ignore DEP004; \ | |
| fi \ | |
| fi | |
| `@if` [ -d ${MARIMO_FOLDER} ]; then \ | |
| if [ -d ${SOURCE_FOLDER} ]; then \ | |
| $(UVX_BIN) -p ${PYTHON_VERSION} deptry ${MARIMO_FOLDER} ${SOURCE_FOLDER} --ignore DEP004; \ | |
| else \ | |
| $(UVX_BIN) -p ${PYTHON_VERSION} deptry ${MARIMO_FOLDER} --ignore DEP004; \ | |
| fi; \ | |
| fi |
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.
In @.rhiza/make.d/quality.mk around lines 18 - 24, The nested shell if/fi
sequence in the Make recipe is missing a command separator between the inner
"fi" and the outer "fi", causing a shell parse error; change the inner "fi \\"
to include a semicolon before the backslash (i.e. "fi; \\") so the shell
receives a proper separator; locate the block that uses MARIMO_FOLDER,
SOURCE_FOLDER and calls $(UVX_BIN) -p ${PYTHON_VERSION} deptry and update the
inner "fi" line to "fi; \\".
| @pytest.fixture(scope="class") | ||
| def classifiers(self, project: dict) -> list[str]: | ||
| """Return the classifiers list.""" | ||
| cl = project.get("classifiers", []) | ||
| if not cl: | ||
| pytest.skip("No classifiers declared in [project]") | ||
| return cl |
There was a problem hiding this comment.
Required sections are currently skippable, which can hide config regressions.
These fixtures skip when data is missing, so required constraints can silently pass instead of failing.
Suggested fix
`@pytest.fixture`(scope="class")
def classifiers(self, project: dict) -> list[str]:
"""Return the classifiers list."""
cl = project.get("classifiers", [])
- if not cl:
- pytest.skip("No classifiers declared in [project]")
+ if not isinstance(cl, list) or not cl:
+ pytest.fail("[project].classifiers must be a non-empty list")
return cl
@@
`@pytest.fixture`(scope="class")
def dependency_groups(self, pyproject: dict) -> dict:
"""Return the [dependency-groups] table."""
dg = pyproject.get("dependency-groups")
- if not isinstance(dg, dict):
- pytest.skip("[dependency-groups] not present")
+ if not isinstance(dg, dict):
+ pytest.fail("pyproject.toml is missing a [dependency-groups] table")
return dgAlso applies to: 169-175
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.
In @.rhiza/tests/structure/test_pyproject.py around lines 145 - 151, The
classifiers fixture currently calls pytest.skip when the "classifiers" key is
missing which hides missing required configuration; change the fixture (def
classifiers) to assert or raise a test failure (e.g., use pytest.fail or a
direct assert) when project.get("classifiers") is empty so the test fails loudly
instead of skipping; apply the same change to the other fixtures in this test
file that currently call pytest.skip (the second similar fixture) so missing
required sections cause test failures rather than silent skips.
| # Create a minimal .rhiza/template.yml | ||
| (tmp_path / ".rhiza" / "template.yml").write_text("repository: Jebel-Quant/rhiza\nref: v0.7.1\n") | ||
|
|
There was a problem hiding this comment.
Sync fixture hardcodes an outdated template ref.
This fixture writes ref: v0.7.1, while this PR updates the template reference to v0.18.8. That drift can make sync tests validate against the wrong template baseline.
Suggested fix
- # Create a minimal .rhiza/template.yml
- (tmp_path / ".rhiza" / "template.yml").write_text("repository: Jebel-Quant/rhiza\nref: v0.7.1\n")
+ # Reuse repository template reference to avoid version drift in sync tests
+ template_src = root / ".rhiza" / "template.yml"
+ if template_src.exists():
+ shutil.copy(template_src, tmp_path / ".rhiza" / "template.yml")
+ else:
+ (tmp_path / ".rhiza" / "template.yml").write_text(
+ "repository: Jebel-Quant/rhiza\nref: v0.18.8\n"
+ )🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.
In @.rhiza/tests/sync/conftest.py around lines 78 - 80, The test fixture is
writing an outdated template ref ("ref: v0.7.1") into the minimal
.rhiza/template.yml; update the string passed to (tmp_path / ".rhiza" /
"template.yml").write_text(...) to use the current template ref "v0.18.8" so
tests validate against the correct baseline and keep the rest of the file
content unchanged.
| @@ -0,0 +1,2 @@ | |||
| --8<-- "README.md" | |||
There was a problem hiding this comment.
README snippet path is likely unresolved with current MkDocs snippet base path.
Line 1 includes README.md, but docs/mkdocs-base.yml currently constrains snippet lookup to base_path: ["."] with docs_dir: docs. That usually resolves inside docs/, so root README.md won’t be found and docs build may fail.
Proposed fix
- --8<-- "README.md"
+ --8<-- "../README.md"Or broaden snippet lookup in docs/mkdocs-base.yml:
- pymdownx.snippets:
- base_path: ["."]
+ base_path: [".", ".."]📝 Committable suggestion
‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.
| --8<-- "README.md" | |
| --8<-- "../README.md" |
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.
In `@docs/index.md` at line 1, The README.md snippet reference in docs/index.md
won't be found because mkdocs-base.yml restricts snippet lookup to base_path:
["."] with docs_dir: docs; update configuration so the snippet lookup includes
the repository root or the README location (for example expand base_path to
include ".." or add the root path), or move/copy the README.md into the docs/
folder and update the reference; edit docs/mkdocs-base.yml (look for base_path
and docs_dir) to add the correct path where README.md resides so the snippet in
docs/index.md resolves during the build.
Summary
v0.18.6tov0.18.8in.rhiza/template.ymlrhiza syncto materialize 77 template files (GitHub workflows, Makefile, tooling config, docs scaffolding, rhiza tests)Test plan
🤖 Generated with Claude Code
Summary by CodeRabbit