Skip to content

Add Python and Rust lint tiers to template#10

Merged
leynos merged 22 commits into
mainfrom
import-lints
May 17, 2026
Merged

Add Python and Rust lint tiers to template#10
leynos merged 22 commits into
mainfrom
import-lints

Conversation

@leynos
Copy link
Copy Markdown
Owner

@leynos leynos commented May 15, 2026

Summary

This branch imports the Python and Rust lint contracts into the Python project template so generated projects exercise the same lint and test tiers that the source repositories use. Python projects now run Ruff followed by PyPy-backed Pylint, and Rust-extension projects add Clippy, Whitaker, and nextest-aware Rust tests after the Python gates.

The generated CI workflow now delegates coverage to leynos/shared-actions through the shared generate-coverage action, with Rust-extension renders passing rust_extension/Cargo.toml as the cargo manifest. The branch also adds black-box local GitHub Actions validation through act and pytest for Python-only and Rust-extension template renders.

This update also addresses review feedback by clarifying README wording, caching generated Rust CI tool installs, failing generated Rust tests early when cargo is missing, installing Whitaker before local generated Rust linting, cleaning generated uv state, respecting the configured Nixie command, relaxing local act validation for Docker or Podman CLIs, tightening act structured log matching, adding action-test assertion messages, reading generated files as UTF-8 in tests, splitting generated lint targets into lint-python and lint-rust, documenting user/developer quality-gate behaviour, recording the quality-gate ADR, validating generated projects through make all, adding generated-file assertions for edge cases, grouping the generated Pylint enable rules by category, and documenting the generated make all gate flow.

No linked issue, roadmap task, or execplan was found for this branch.

Review walkthrough

Validation

  • pytest -q: passed, 3 passed in 110.37s; log: /tmp/test-agent-template-python-import-lints.out.
  • pytest -q: passed after bytecode cleanup, 3 passed in 111.15s; log: /tmp/test-agent-template-python-import-lints-pr-bytecode-removal.out.
  • pytest -q: passed after shared coverage action tests were added, 3 passed, 2 skipped in 112.89s; log: /tmp/test-agent-template-python-import-lints-final.out.
  • RUN_ACT_VALIDATION=1 pytest -q tests/test_github_actions_integration.py::test_python_only_workflow_runs_with_shared_coverage_action: xfailed after the generated workflow ran Python tests and produced coverage.xml; log: /tmp/act-python-agent-template-python-import-lints.out.
  • RUN_ACT_VALIDATION=1 pytest -q tests/test_github_actions_integration.py::test_rust_extension_workflow_runs_with_shared_coverage_action: xfailed after the generated workflow observed the Rust and Python test paths and produced coverage.xml; log: /tmp/act-rust-agent-template-python-import-lints.out.
  • pytest -q: passed after workflow validation review fixes, 3 passed, 2 skipped in 106.43s; log: /tmp/test-agent-template-python-import-lints-code-review-comments.out.
  • RUN_ACT_VALIDATION=1 pytest -q tests/test_github_actions_integration.py -m act: xfailed at the known shared-action archive boundary for both generated workflows, 2 xfailed in 631.81s; log: /tmp/act-agent-template-python-import-lints-code-review-comments.out.
  • pytest -q: passed after architecture review fixes, 3 passed, 2 skipped in 109.01s; log: /tmp/test-agent-template-python-import-lints-architecture-review.out.
  • pytest -q: passed after make all follow-up commit, 3 passed, 2 skipped in 128.19s; log: /tmp/test-agent-template-python-import-lints-make-all-followup-post-commit.out.
  • pytest -q: passed after assertion-message follow-up commit, 3 passed, 2 skipped in 110.39s; log: /tmp/test-agent-template-python-import-lints-assert-messages-post-commit.out.
  • pytest -q: passed after UTF-8 read follow-up, 3 passed, 2 skipped in 113.71s; log: /tmp/test-agent-template-python-import-lints-utf8-read.out.
  • pytest -q: passed after UTF-8 read follow-up commit, 3 passed, 2 skipped in 112.02s; log: /tmp/test-agent-template-python-import-lints-utf8-read-post-commit.out.
  • git diff --check HEAD: passed with no output; log: /tmp/diff-check-agent-template-python-import-lints-utf8-read-post-commit.out.
  • git push: passed; log: /tmp/push-agent-template-python-import-lints-utf8-read.out.
  • pytest -q: passed after parametrizing act workflow tests, 3 passed, 2 skipped in 119.44s; log: /tmp/test-agent-template-python-import-lints-act-parametrize.out.
  • RUN_ACT_VALIDATION=1 pytest -q tests/test_github_actions_integration.py -m act: xfailed at the known shared-action archive boundary for both parametrized generated workflows, 2 xfailed in 606.10s; log: /tmp/act-agent-template-python-import-lints-act-parametrize.out.
  • pytest -q: passed after parametrized act workflow test commit, 3 passed, 2 skipped in 111.48s; log: /tmp/test-agent-template-python-import-lints-act-parametrize-postcommit.out.
  • RUN_ACT_VALIDATION=1 pytest -q tests/test_github_actions_integration.py -m act: xfailed at the known shared-action archive boundary for both parametrized generated workflows, 2 xfailed in 622.06s; log: /tmp/act-agent-template-python-import-lints-act-parametrize-postcommit.out.
  • git diff --check HEAD: passed with no output after parametrizing act workflow tests.
  • git push: passed; log: /tmp/push-agent-template-python-import-lints-act-parametrize.out.

Notes

  • Firecrawl verification found maturin latest as 1.13.3 on PyPI and pyo3 latest as 0.28.3 on crates.io before the dependency updates were applied.
  • The generated Rust lint target runs Whitaker from inside the generated Rust crate because Whitaker expects cargo metadata to resolve from a directory containing a Cargo.toml.
  • The compile-time UI and full snapshot-test warnings were not expanded into new test frameworks in this branch. The branch now adds focused generated-file assertions for the immediate Makefile and rendering contracts without introducing trybuild, syrupy, or insta infrastructure into this template PR.
  • The act xfail is intentionally narrow: the local workflows reached the generated Python/Rust tests and coverage output, then failed in the shared generate-coverage composite action output/archive phase with Parameter INPUT_ARTEFACT_NAME_SUFFIX specified multiple times and an empty upload-artifact name under act. This is retained as an explicit local-runner limitation rather than treating unrelated workflow failures as acceptable.

Pytest and others added 5 commits May 15, 2026 12:44
Import the PyPy-backed Pylint tier after Ruff, add Rust Clippy,
Whitaker, and nextest-aware test wiring for generated Rust extension
projects, and update the stub modules so the stricter gates pass.

Validate generated Python-only and Rust projects through their public
Makefile gates so template regressions fail in the template test suite.
Keep the template branch focused on source and configuration changes by
removing a committed Python bytecode artefact from the test tree.
Keep the template branch focused on source and configuration changes by
removing committed Python bytecode and ignoring local pytest cache output.
Copy link
Copy Markdown

@sourcery-ai sourcery-ai Bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Sorry @leynos, you have reached your weekly rate limit of 2500000 diff characters.

Please try again later or upgrade to continue using Sourcery

@coderabbitai
Copy link
Copy Markdown

coderabbitai Bot commented May 15, 2026

Review Change Stack

Note

Reviews paused

Use the following commands to manage reviews:

  • @coderabbitai resume to resume automatic reviews.
  • @coderabbitai review to trigger a single review.

Use the checkboxes below for quick actions:

  • ▶️ Resume reviews
  • 🔍 Trigger review

Walkthrough

Modernise the copier template: add a reusable Makefile, refactor tests to run generated quality gates (make all), upgrade optional Rust extension to edition 2024 and PyO3 0.28.3 with stricter lints, extend tooling/pyproject templates, and add local act validation, docs and socket helpers.

Changes

Template System Modernisation

Layer / File(s) Summary
Project setup and docs
.gitignore, README.md, scripts/setup_test_deps.sh, docs/*
Add __pycache__/ and .pytest_cache/ to .gitignore; update README “Running Tests” to document per-generated-project toolchains and to install only pytest-copier in the example; scripts/setup_test_deps.sh now installs only pytest-copier; add quality-gate diagram and developer/user guides.
Makefile orchestration and CI
template/Makefile.jinja, template/.github/workflows/ci.yml.jinja
Add template/Makefile.jinja implementing .venv, all, fmt, check-fmt, lint (split lint-python/lint-rust), typecheck, test, markdownlint, nixie, help, ensure_tool macros and conditional Rust wiring; CI template moves to shared generate-coverage action, conditionally sets WHITAKER_INSTALLER_REV, and includes Rust setup/caching and pinned Whitaker installer when use_rust is enabled.
Python tooling, packaging and template wiring
template/pyproject.toml.jinja, template/tests/test_stub.py.jinja, template/{{ package_name }}/__init__.py.jinja, template/{{ package_name }}/pure.py, pytest.ini
Extend dev dependency groups; set Ruff target-version from python_version; adjust Ruff selects and per-file ignores; add flake8-tidy-imports.banned-api and pydocstyle = numpy; replace minimal Pylint settings with comprehensive configuration; add generated test_stub.py.jinja; switch optional Rust import to importlib.import_module with typing.cast; add module docstring to pure.py; add act pytest marker.
Rust extension modernisation
template/{% if use_rust %}rust_extension{% endif %}/Cargo.toml.jinja, .../src/lib.rs.jinja, .../clippy.toml
Upgrade crate edition to 2024, bump pyo3 to 0.28.3, add extensive [lints.clippy], [lints.rust], [lints.rustdoc]; adjust clippy.toml thresholds and allow expect in tests; update lib.rs.jinja to use Bound<'_, PyModule>, add #[expect(...)] on pyfunctions and an inline unit test; tighten maturin requirement and set tool.maturin.python-source = ".".
Tests, act integration and docs
tests/conftest.py, tests/test_template.py, tests/fixtures/pull_request.event.json, tests/test_github_actions_integration.py, template/docs/local-validation-of-github-actions-with-act-and-pytest.md, docs/developers-guide.md, docs/adr-001-template-quality-gates.md, template/docs/users-guide.md.jinja
Add session fixture copier_template_paths; refactor tests to execute run_quality_gates and validate runtime import via uv run; add PR event fixture and tests/test_github_actions_integration.py to validate workflows locally with act and parse JSON logs; add comprehensive local validation guide and ADR/developer/user documentation.
Test utilities
tests/utilities.py
Add Docker/Podman socket resolution and validation helpers, docker_environment() normalisation, and container_daemon_socket() to support act-based integration tests.

Sequence Diagram(s)

sequenceDiagram
  participant Tester as "pytest harness"
  participant Act as "act (local runner)"
  participant GeneratedCI as "Rendered CI workflow"
  participant ArtifactDir as "artifact-server-path"

  Tester->>Act: run `act pull_request -j lint-test --json --artifact-server-path`
  Act->>GeneratedCI: execute workflow steps
  GeneratedCI->>ArtifactDir: upload `coverage.xml` (Cobertura)
  GeneratedCI->>Tester: emit JSON logs parsed by tests
Loading

Possibly Related PRs

  • leynos/beatcue#10: The Makefile changes adopt the same PyPy-backed pylint shim and two-tier lint layout used in this PR.
  • leynos/prosidy-darn#10: Implements the same Ruff→PyPy-backed Pylint flow and Makefile plumbing referenced by this PR.
  • leynos/stilyagi#13: Mirrors the Makefile lint-tier wiring and PyPy-backed Pylint invocation introduced here.

Templates modernise, Makefiles sing,
Gates run in order, checks take wing.
Rust steps forward, lints stand tall,
Local act inspects the CI hall.
Small edits ripple through the template wall.


Caution

Pre-merge checks failed

Please resolve all errors before merging. Addressing warnings is optional.

  • Ignore

❌ Failed checks (2 errors, 1 warning, 1 inconclusive)

Check name Status Explanation Resolution
Testing (Overall) ❌ Error Unfixed review comment: template/Makefile.jinja help target uses awk FS=":" instead of FS=":.*##". Tests are substantive but lack help target coverage. Apply proposed fix from review comments to Makefile.jinja help target: change awk FS=":" to FS=":.*##" and add gsub whitespace trimming.
Unit Architecture ❌ Error Help target awk FS=":" splits incorrectly including prerequisites. assert_act_result mixes query and command: reads files, parses logs, asserts, then conditionally xfails tests. Fix help: use FS=":.*##" with gsub. Refactor assert_act_result to split artifact/log queries from xfail side-effect into separate phases or functions.
Testing (Unit And Behavioural) ⚠️ Warning Tests are predominantly behavioural (make all + imports). No unit tests for utilities.py functions, Makefile variables, or error paths. Help target formatting fix not tested. Add unit tests for utilities.py (socket validation, DOCKER_HOST handling). Test Makefile help output, cargo-missing errors. Add parametrised edge case tests (special chars, empty names).
Security And Privacy ❓ Inconclusive Curl | bash pattern in CodeScene installer flagged in review. PR objectives note this was deferred to a GitHub issue. Other security controls are properly implemented. Replace curl | bash in ci.yml.jinja line 88 with safer mechanism (pinned binaries, checksums, or official action).
✅ Passed checks (14 passed)
Check name Status Explanation
Title check ✅ Passed The title clearly summarises the main change: importing Python and Rust lint tiers into the template to enable generated projects to exercise the same lint contracts.
Description check ✅ Passed The description is comprehensive and directly related to the changeset, documenting the import of lint and test contracts, configuration imports, workflow updates, and testing validation.
Docstring Coverage ✅ Passed Docstring coverage is 91.67% which is sufficient. The required threshold is 80.00%.
Linked Issues check ✅ Passed Check skipped because no linked issues were found for this pull request.
Out of Scope Changes check ✅ Passed Check skipped because no linked issues were found for this pull request.
User-Facing Documentation ✅ Passed User-facing changes documented across README, users-guide template, developers guide, and ADR-001 for appropriate audiences.
Developer Documentation ✅ Passed Developers-guide documents Makefile architecture, CI strategy, Rust integration, and test approach with tool pins exposed. ADR-001 records design decisions. No multi-language docs or roadmaps present.
Module-Level Documentation ✅ Passed All modules have docstrings. Four repository modules clearly explain purpose, function, and relationships. One template module has appropriate stub documentation.
Testing (Property / Proof) ✅ Passed No unbounded invariants or unproven assumptions warranting property testing or formal verification are introduced by this PR.
Testing (Compile-Time / Ui) ✅ Passed Tests assert key generated-file contracts (structure, references, behaviour). ADR-001 explicitly rejects snapshots. Not applicable to template generators; no compile-time tests needed.
Domain Architecture ✅ Passed Domain and infrastructure properly segregated. Domain code has no adapter/framework leakage. Tests separate domain from infrastructure. Make targets enforce boundaries at correct layers.
Observability ✅ Passed PR provides observability at failure boundaries: clear error messages for missing cargo/tools, descriptive test assertions. Lacks execution logs and metrics, acceptable for developer-facing template.
Performance And Resource Use ✅ Passed No algorithmic regressions or unbounded resource growth detected. JSON parsing is O(n). Memory allocations are bounded. Subprocess I/O has appropriate 1200s timeout. No nested loops.
Concurrency And State ✅ Passed No concurrency, threading, locks, or shared mutable state in this PR. Test isolation via tmp_path. Sequential execution via Makefile. Check not applicable.
✨ Finishing Touches
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Commit unit tests in branch import-lints

Comment @coderabbitai help to get the list of available commands and usage tips.

@leynos leynos marked this pull request as ready for review May 15, 2026 12:24
@coderabbitai coderabbitai Bot added the Roadmap label May 15, 2026
Copy link
Copy Markdown

@chatgpt-codex-connector chatgpt-codex-connector Bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

💡 Codex Review

Here are some automated review suggestions for this pull request.

Reviewed commit: d1827e1339

ℹ️ About Codex in GitHub

Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you

  • Open a pull request for review
  • Mark a draft as ready
  • Comment "@codex review".

If Codex has suggestions, it will comment; otherwise it will react with 👍.

Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".

Comment thread template/.github/workflows/ci.yml.jinja Outdated
Copy link
Copy Markdown

@coderabbitai coderabbitai Bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 7

🤖 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 `@README.md`:
- Line 14: Replace the ambiguous phrase "Pylint through the PyPy shim" in
README.md with explicit wording clarifying whether Pylint is executed under PyPy
or via a wrapper; update the text to something like "Pylint (via a PyPy-backed
runner)" or "Pylint run under the PyPy interpreter" depending on the actual
implementation, ensuring the original phrase "Pylint through the PyPy shim" is
removed and the chosen clarification is used inline with the list that includes
"Ruff", "`ty`", and "pytest".
- Around line 15-16: Replace the ambiguous sentence "Ensure these tools are
available before running `pytest`" in README.md with a clarified paragraph that
states the test suite requires only the `pytest-copier` plugin to render and
validate generated projects, and that generated projects themselves install and
run their own tooling (Ruff, Pylint via PyPy, `ty`, pytest, and—if Rust is
enabled—Clippy, Whitaker, and nextest); specifically locate the sentence
fragment "Ensure these tools are available before running `pytest`" and swap it
for the proposed clarification so readers know to install only the
`pytest-copier` test dependency, not all listed tools.

In `@template/.github/workflows/ci.yml.jinja`:
- Around line 29-45: Add a caching step before the "Install Rust lint and test
tools" step to avoid re-installing cargo-nextest and whitaker-installer every CI
run: insert a new job step after the "Set up Rust" step that uses a cache action
(e.g., Swatinem/rust-cache@v2 or actions/cache) configured to cache Rust tool
installs and cargo bin targets so subsequent runs restore the cache and skip
re-installation; reference the existing step names "Set up Rust" and the tools
"cargo-nextest" and "whitaker-installer" when placing and configuring the cache
step.

In `@template/Makefile.jinja`:
- Around line 19-33: The TEST_CMD detection can silently succeed when CARGO is
just the literal "cargo" even if cargo is not installed, causing later test
execution to fail; add an explicit cargo availability check (e.g., define a
CARGO_AVAILABLE variable that runs command -v $(CARGO) or similar) and use it in
the test target or when computing TEST_CMD to error out with a clear message if
cargo is missing, updating references to CARGO, TEST_CMD and the test target to
bail early with a helpful message rather than falling back to an unusable
"cargo" invocation.

In `@tests/test_template.py`:
- Around line 45-47: The three bare assertions in tests/test_template.py —
assert (proj / "rust_extension").exists(), assert (proj / "docs" /
"rust-extension.md").exists(), and assert "maturin" in (proj /
"pyproject.toml").read_text() — should include descriptive assertion messages;
update each to use the form assert <condition>, "<message>" with clear messages
like "rust_extension directory should exist for Rust template", "Rust
documentation should be generated for Rust template", and "maturin should be in
pyproject.toml for Rust template" so failures produce helpful output.
- Around line 62-64: Add descriptive assertion messages to the two bare asserts
in tests/test_template.py: update the assertion checking (proj /
"rust_extension").exists() to include a message like "rust_extension directory
should exist for custom package Rust template" and update the assertion
verifying "custom_pkg" in text (from pyproject.toml) to include a message like
"custom package name should appear in pyproject.toml"; modify the assertions in
the same test where these symbols appear so failures report clear reasons.
- Around line 29-31: The three bare assertions should include descriptive
messages: update the assertions that reference (proj /
"rust_extension").exists(), (proj / "docs" / "rust-extension.md").exists(), and
"maturin" not in (proj / "pyproject.toml").read_text() to use the assert ...,
"message" form so failures state which condition failed (e.g., indicate that the
rust_extension directory, the rust-extension.md doc, or the maturin entry in
pyproject.toml should not exist for the Python-only template).
🪄 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: Organization UI

Review profile: ASSERTIVE

Plan: Pro Plus

Run ID: 2d5aec83-2a51-4573-80d9-eb605b1d8503

📥 Commits

Reviewing files that changed from the base of the PR and between 5a4428d and d1827e1.

📒 Files selected for processing (15)
  • .gitignore
  • README.md
  • scripts/setup_test_deps.sh
  • template/.github/workflows/ci.yml.jinja
  • template/Makefile
  • template/Makefile.jinja
  • template/pyproject.toml.jinja
  • template/tests/test_stub.py.jinja
  • template/{% if use_rust %}rust_extension{% endif %}/Cargo.toml.jinja
  • template/{% if use_rust %}rust_extension{% endif %}/clippy.toml
  • template/{% if use_rust %}rust_extension{% endif %}/src/lib.rs.jinja
  • template/{{ package_name }}/__init__.py.jinja
  • template/{{ package_name }}/pure.py
  • tests/conftest.py
  • tests/test_template.py
💤 Files with no reviewable changes (1)
  • template/Makefile

Comment thread README.md Outdated
Comment thread README.md Outdated
Comment thread template/.github/workflows/ci.yml.jinja
Comment thread template/Makefile.jinja
Comment thread tests/test_template.py Outdated
Comment thread tests/test_template.py Outdated
Comment thread tests/test_template.py Outdated
Replace the generated workflow's manual coverage step with the shared
`leynos/shared-actions` coverage action and shared Rust setup action. Keep
Rust-extension projects on a cargo manifest path that works from the Python
project root.

Add an opt-in `act` pytest harness that renders Python-only and Rust-extension
projects, runs the generated CI workflow as a black-box integration test, and
asserts that coverage and template tests are exercised. Record the current
`act` composite-action archive limitation as a narrow xfail after coverage is
produced.
@leynos leynos marked this pull request as draft May 15, 2026 13:11
Clarify README wording around the PyPy-backed Pylint runner and the local
pytest dependency. Cache Rust tool installs in generated CI, make generated
Rust tests fail early when cargo is unavailable, and add assertion messages to
template validation tests.
Copy link
Copy Markdown

@coderabbitai coderabbitai Bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 2

🤖 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 `@tests/test_github_actions_integration.py`:
- Around line 139-180: The two near-duplicate tests
(test_python_only_workflow_runs_with_shared_coverage_action and
test_rust_extension_workflow_runs_with_shared_coverage_action) should be
collapsed into a single parametrized test; create a fixture act_ready that calls
require_act() and inject it into the new
test_generated_workflow_runs_with_shared_coverage_action, parameterize over
(name, package, use_rust, artifact_dir), update copier.copy to use tmp_path /
package and the parameterized values, call prepare_git_repository(project) and
run_act(project, artifact_dir=tmp_path / artifact_dir), and conditionally assert
the Rust-specific line in the workflow only when use_rust is True; ensure you
reuse symbols prepare_git_repository, run_act, and assert_act_result and place
the act_ready fixture in conftest.py or fixtures per guidelines.
- Around line 57-59: The test builds an "act" command and always appends
"--container-daemon-socket" with env.get("DOCKER_HOST", "") which can pass an
empty string; change the logic in tests/test_github_actions_integration.py where
the command list is constructed (variable name "command") to only append the
flag when env.get("DOCKER_HOST") returns a non-empty value: fetch docker_host =
env.get("DOCKER_HOST") and if docker_host truthy then extend command with
["--container-daemon-socket", docker_host], otherwise do not add the flag.
🪄 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: Organization UI

Review profile: ASSERTIVE

Plan: Pro Plus

Run ID: f4943c00-0c52-4b4c-b027-4bdc032dc5f5

📥 Commits

Reviewing files that changed from the base of the PR and between d1827e1 and e969a97.

📒 Files selected for processing (5)
  • pytest.ini
  • template/.github/workflows/ci.yml.jinja
  • template/docs/local-validation-of-github-actions-with-act-and-pytest.md
  • tests/fixtures/pull_request.event.json
  • tests/test_github_actions_integration.py

Comment thread tests/test_github_actions_integration.py Outdated
Comment thread tests/test_github_actions_integration.py Outdated
Group the generated Pylint `enable` list into the requested logging, pattern
matching, control-flow, iterator, runtime, text hygiene, mutation, and
complexity categories without changing the rule set.
@leynos leynos marked this pull request as ready for review May 15, 2026 21:37
@sourcery-ai
Copy link
Copy Markdown

sourcery-ai Bot commented May 15, 2026

Reviewer's Guide

Imports Python (Ruff, PyPy-backed Pylint, ty) and Rust (Clippy, Whitaker, nextest) lint/test tiers into the Python template, wires CI coverage to a shared composite action, and adds black-box GitHub Actions validation via act and pytest plus corresponding config, docs, and tests updates.

Sequence diagram for CI workflow with shared coverage and Rust tiers

sequenceDiagram
    actor Dev
    participant GitHub_Actions as GitHubActions_ci.yml
    participant Setup as setup_steps
    participant Make as make_targets
    participant Coverage as generate_coverage_action
    participant CodeScene as CodeScene_CLI

    Dev->>GitHub_Actions: push or pull_request
    GitHub_Actions->>Setup: actions/checkout
    GitHub_Actions->>Setup: setup-uv
    alt use_rust
        GitHub_Actions->>Setup: leynos/setup-rust
        GitHub_Actions->>Setup: rust-cache
        GitHub_Actions->>Setup: install whitaker-installer
    end
    GitHub_Actions->>Setup: uv tool install mbake

    GitHub_Actions->>Make: make lint
    GitHub_Actions->>Make: make typecheck
    GitHub_Actions->>Make: make test
    Note over Make: runs ruff, PyPy-backed pylint, ty, pytest
    alt use_rust
        Note over Make: also runs cargo doc, clippy, whitaker, nextest
    end

    GitHub_Actions->>Coverage: leynos/shared-actions generate-coverage
    Note over Coverage: uses coverage.xml
    alt use_rust
        Note over Coverage: cargo-manifest=rust_extension/Cargo.toml
    end

    GitHub_Actions->>CodeScene: CodeScene Coverage CLI
    CodeScene-->>GitHub_Actions: coverage upload status
Loading

Flow diagram for new Makefile lint and test tiers

flowchart LR
    Dev[Developer runs make all] --> All[all]
    All --> Build["build (uv sync dev deps)"]
    All --> CheckFmt[check-fmt]
    All --> Lint[lint]
    All --> Typecheck[typecheck]
    All --> Test[test]

    Lint --> RuffCheck[ruff check]
    Lint --> PylintPyPy[pylint-pypy via PyPy]
    Typecheck --> TyCheck[ty check]
    Test --> Pytest[pytest -v -n auto]

    subgraph Rust_extension_enabled[use_rust == true]
        Lint --> RustDoc[cargo doc]
        Lint --> Clippy[cargo clippy]
        Lint --> Whitaker[whitaker --all]
        Test --> Nextest[cargo nextest run]
        Test --> RustDocTests[cargo test --doc]
    end
Loading

File-Level Changes

Change Details Files
Align generated Python project tooling and configuration with source repo lint/typecheck/test stack.
  • Add ty to dev dependencies and configure Ruff target-version based on template python_version
  • Tighten Ruff config: per-file ignores (including init.py), add flake8-tidy-imports banned typing generics, enable numpy pydocstyle convention
  • Introduce Pylint configuration under [tool.pylint.*] with grouped enable lists by concern (logging, pattern matching, control flow, collections, APIs, layout, mutation, complexity) and basic design/main settings
  • Adjust build-system blocks: bump maturin to >=1.13.3,<2 for Rust templates, change python-source to '.', and add hatch wheel target packages for non-Rust templates
template/pyproject.toml.jinja
Replace bespoke static checks/build logic in template tests with execution of the generated Makefile gates and in-env import checks.
  • Remove helper functions that pip-install the generated package and run ruff/pyright directly
  • Add run_quality_gates to execute make check-fmt, make lint, make typecheck, and make test in rendered projects
  • Add check_generated_import that uses uv run to import the generated package and assert hello() greeting, parameterised by expected greeting
  • Update Python-only, Rust, and custom-package template tests to use new helpers and add explicit assertion messages for template structure and pyproject contents
tests/test_template.py
Introduce a template Makefile that defines the canonical quality gates for both Python-only and Rust-extension projects.
  • Add tool discovery helpers and variables (UV, pytest, Pylint PyPy shim, markdown tools, optional Rust tool paths)
  • Define fmt, check-fmt, lint, typecheck, markdownlint, nixie, test, and aggregate all targets, wiring Ruff, Pylint (PyPy-backed), pytest-xdist, and optional Rust tooling
  • Conditionally configure Rust targets: environment flags, cargo detection, clippy, doc builds, Whitaker run inside rust_extension crate, and cargo/nextest-based test execution with early failure if cargo unavailable
template/Makefile.jinja
Upgrade and harden the Rust extension crate to modern standards with strict linting and docs requirements.
  • Update Cargo.toml template to edition 2024 and PyO3 0.28.3
  • Add clippy, rust, and rustdoc lints sections with pedantic and domain-specific deny rules (hygiene, debugging remnants, panic-prone ops, portability, idioms, numeric issues, API ergonomics, error handling, and documentation)
  • Introduce clippy.toml with CodeScene-aligned complexity and argument thresholds while allowing expect in tests
  • Update lib.rs stub to include crate-level docs, modern PyO3 module signature, clippy expect attribute, and add a small unit test for hello()
template/{% if use_rust %}rust_extension{% endif %}/Cargo.toml.jinja
template/{% if use_rust %}rust_extension{% endif %}/clippy.toml
template/{% if use_rust %}rust_extension{% endif %}/src/lib.rs.jinja
Wire generated CI workflows to shared coverage generation and add Rust-tool caching and setup when rust_extension is enabled.
  • Set Rust-related env vars and Whitaker installer rev in CI when use_rust is true
  • Add shared-actions setup-rust, cache Rust lint/test tool binaries, and install whitaker-installer + Whitaker with cranelift backend
  • Remove inline slipcover-based coverage run and artifact upload; replace with shared leynos/shared-actions generate-coverage composite action, parameterising output path, format, artefact suffix, and optional cargo-manifest for Rust builds
  • Limit uv tool installation to mbake (drop ty/ruff from global tools; generated projects manage them)
template/.github/workflows/ci.yml.jinja
Improve the generated Python package’s Rust-bridge import and stub testing story.
  • Change init.py to use importlib.import_module for the Rust extension with proper package context, add TYPE_CHECKING gate importing collections.abc, and type-cast hello to a Callable[[], str]; fall back to pure.hello when module is missing
  • Clarify README and setup_test_deps.sh so only pytest-copier is installed at template-dev time and generated projects own their lint/type tooling
  • Add a template test file that asserts {{ package_name }}.hello() returns the correct greeting depending on whether use_rust is enabled
template/{{ package_name }}/__init__.py.jinja
template/{{ package_name }}/pure.py
README.md
scripts/setup_test_deps.sh
template/tests/test_stub.py.jinja
Add black-box GitHub Actions CI validation via act, plus supporting pytest configuration and docs.
  • Introduce tests/test_github_actions_integration.py that renders Python-only and Rust-extension projects, initialises git, runs act on the generated lint-test workflow, parses act --json logs to confirm Python/Rust tests and shared coverage action executed, and xfails narrowly on a known composite-output bug
  • Create pytest.ini with an act marker and conftest.py to restrict pytest-copier template paths to copier.yml and template
  • Add documentation on local validation of GitHub Actions with act and pytest, emphasising artefact/log assertions and known limitations
  • Ship a pull_request.event.json fixture used by the act-based tests
tests/test_github_actions_integration.py
pytest.ini
tests/conftest.py
template/docs/local-validation-of-github-actions-with-act-and-pytest.md
tests/fixtures/pull_request.event.json

Tips and commands

Interacting with Sourcery

  • Trigger a new review: Comment @sourcery-ai review on the pull request.
  • Continue discussions: Reply directly to Sourcery's review comments.
  • Generate a GitHub issue from a review comment: Ask Sourcery to create an
    issue from a review comment by replying to it. You can also reply to a
    review comment with @sourcery-ai issue to create an issue from it.
  • Generate a pull request title: Write @sourcery-ai anywhere in the pull
    request title to generate a title at any time. You can also comment
    @sourcery-ai title on the pull request to (re-)generate the title at any time.
  • Generate a pull request summary: Write @sourcery-ai summary anywhere in
    the pull request body to generate a PR summary at any time exactly where you
    want it. You can also comment @sourcery-ai summary on the pull request to
    (re-)generate the summary at any time.
  • Generate reviewer's guide: Comment @sourcery-ai guide on the pull
    request to (re-)generate the reviewer's guide at any time.
  • Resolve all Sourcery comments: Comment @sourcery-ai resolve on the
    pull request to resolve all Sourcery comments. Useful if you've already
    addressed all the comments and don't want to see them anymore.
  • Dismiss all Sourcery reviews: Comment @sourcery-ai dismiss on the pull
    request to dismiss all existing Sourcery reviews. Especially useful if you
    want to start fresh with a new review - don't forget to comment
    @sourcery-ai review to trigger a new review!

Customizing Your Experience

Access your dashboard to:

  • Enable or disable review features such as the Sourcery-generated pull request
    summary, the reviewer's guide, and others.
  • Change the review language.
  • Add, remove or edit custom review instructions.
  • Adjust other review settings.

Getting Help

sourcery-ai[bot]

This comment was marked as resolved.

Copy link
Copy Markdown

@chatgpt-codex-connector chatgpt-codex-connector Bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

💡 Codex Review

Here are some automated review suggestions for this pull request.

Reviewed commit: dc991f9bda

ℹ️ About Codex in GitHub

Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you

  • Open a pull request for review
  • Mark a draft as ready
  • Comment "@codex review".

If Codex has suggestions, it will comment; otherwise it will react with 👍.

Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".

Comment thread template/Makefile.jinja
Make generated Rust lint targets depend on a `whitaker` target that no-ops when
Whitaker is already present and otherwise installs the pinned Whitaker
installer before running the local lint gate.

Document the generated `make all` quality-gate flow with an accessible Mermaid
caption so local Python and Rust gate behaviour is visible from the README.
@coderabbitai coderabbitai Bot added Issue and removed Roadmap labels May 15, 2026
Copy link
Copy Markdown

@coderabbitai coderabbitai Bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 1

🤖 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 `@template/Makefile.jinja`:
- Around line 86-97: The whitaker target may call whitaker-installer before
~/.cargo/bin is on PATH; update the whitaker target so the post-install
invocation runs with a PATH prefixed by $(USER_BIN_PATH) (e.g., invoke
whitaker-installer with PATH="$(USER_BIN_PATH):$$PATH" whitaker-installer
--cranelift) to ensure the newly installed binary is resolvable; keep the
existing cargo install invocation and use the same variables $(WHITAKER),
$(CARGO_AVAILABLE), $(CARGO), and $(WHITAKER_INSTALLER_REV).
🪄 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: Organization UI

Review profile: ASSERTIVE

Plan: Pro Plus

Run ID: 6660e7bb-f984-4e0a-a18a-6de01b3ab6e4

📥 Commits

Reviewing files that changed from the base of the PR and between dc991f9 and db5fce6.

📒 Files selected for processing (2)
  • README.md
  • template/Makefile.jinja

Comment thread template/Makefile.jinja
Clean generated uv cache directories, allow act validation to use Docker or
Podman, and tighten structured act log matching so Python, Rust, and coverage
signals must come from the shared coverage step.

Expand Markdown acronyms in the local GitHub Actions validation guide.
Copy link
Copy Markdown

@coderabbitai coderabbitai Bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 2

🤖 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 `@template/Makefile.jinja`:
- Around line 131-133: The Makefile target uses ensure_tool with the literal
"nixie", ignoring the NIXIE variable and any overrides; update the nixie target
to call ensure_tool with the configured command by passing $(NIXIE) (or the
appropriate expansion) instead of the hard-coded name so the runtime check
validates the actual command in the NIXIE variable; change the ensure_tool
invocation in the nixie target from ensure_tool,nixie to ensure_tool,$(NIXIE)
and keep the subsequent use of $(NIXIE) --no-sandbox.

In `@tests/test_github_actions_integration.py`:
- Line 153: Several bare asserts in tests/test_github_actions_integration.py
(notably the check for (project / "coverage.xml").exists() and the asserts
around lines 184, 207, 208) lack messages; update each bare assert to include a
clear failure message so CI failures are actionable. Locate the assert
expressions (e.g., the assertion verifying coverage.xml existence and the
subsequent file/path asserts) and replace them with assertions that include a
concise explanatory string (for example describing which file or workflow
artifact is missing) so test failures report exactly what was not found.
🪄 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: Organization UI

Review profile: ASSERTIVE

Plan: Pro Plus

Run ID: e8eba249-193d-4cec-b2b0-ea9d27b99536

📥 Commits

Reviewing files that changed from the base of the PR and between db5fce6 and 26f8c8b.

📒 Files selected for processing (3)
  • template/Makefile.jinja
  • template/docs/local-validation-of-github-actions-with-act-and-pytest.md
  • tests/test_github_actions_integration.py

Comment thread template/Makefile.jinja
Comment thread tests/test_github_actions_integration.py Outdated
Add module documentation for template tests, split generated linting into
language-specific targets, and document the generated quality gate design in
user, developer, and ADR docs.

Add focused generated-file assertions for Makefile lint target structure,
clean-up paths, and cargo failure messaging. Keep act log parsing separated
from workflow assertions so JSON format handling stays isolated.
@coderabbitai coderabbitai Bot added Roadmap and removed Issue labels May 15, 2026
Copy link
Copy Markdown

@coderabbitai coderabbitai Bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 2

🤖 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 `@docs/adr-001-template-quality-gates.md`:
- Around line 9-12: The context statement sentence "Generated projects need one
public local gate that is simple to run, while still keeping Python and Rust
tooling independently understandable. The template also needs generated GitHub
Actions workflows to match the local gate closely enough that failures are
predictable." is awkward; replace it with a single clear sentence such as:
"Generated projects must include one public local gate that is simple to run and
preserve independent clarity for Python and Rust tooling, and the template
should generate GitHub Actions workflows that closely match the local gate so
failures are predictable." Ensure en‑GB spelling/grammar (oxendict) is used when
applying the rewrite.

In `@tests/test_template.py`:
- Around line 10-15: The test currently invokes individual make targets; update
run_quality_gates to run the public aggregate target instead by replacing the
sequence of project.run("make check-fmt"), project.run("make lint"),
project.run("make typecheck"), project.run("make test") with a single
project.run("make all") call so the generated project is validated through the
same public entrypoint; locate the function run_quality_gates (and its use of
the CopierProject instance) and change the calls accordingly.
🪄 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: Organization UI

Review profile: ASSERTIVE

Plan: Pro Plus

Run ID: 54e621d3-d54d-4ecc-b2b3-08004e1c2e73

📥 Commits

Reviewing files that changed from the base of the PR and between 26f8c8b and ed48249.

📒 Files selected for processing (6)
  • docs/adr-001-template-quality-gates.md
  • docs/developers-guide.md
  • template/Makefile.jinja
  • template/docs/users-guide.md.jinja
  • tests/test_github_actions_integration.py
  • tests/test_template.py

Comment thread docs/adr-001-template-quality-gates.md Outdated
Comment thread tests/test_template.py Outdated
@leynos

This comment was marked as resolved.

Copy link
Copy Markdown

@coderabbitai coderabbitai Bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 1

♻️ Duplicate comments (3)
tests/test_github_actions_integration.py (2)

67-68: ⚠️ Potential issue | 🟠 Major | ⚡ Quick win

Omit --container-daemon-socket when DOCKER_HOST is not configured.

Do not pass --container-daemon-socket with an empty string. The flag exhibits unreliable behaviour on some systems when passed via CLI with an empty value. Omit the flag entirely unless DOCKER_HOST is explicitly set.

Proposed fix
     command = [
         "act",
         "pull_request",
         "-j",
         "lint-test",
         "-e",
         str(EVENT),
         "-P",
         ACT_IMAGE,
         "--artifact-server-path",
         str(artifact_dir),
         "--json",
         "-b",
-        "--container-daemon-socket",
-        env.get("DOCKER_HOST", ""),
     ]
+    docker_host = env.get("DOCKER_HOST")
+    if docker_host:
+        command.extend(["--container-daemon-socket", docker_host])
🤖 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 `@tests/test_github_actions_integration.py` around lines 67 - 68, The test
currently always passes the "--container-daemon-socket" flag with
env.get("DOCKER_HOST", "") which can result in an empty-string CLI arg; change
the construction so you only append the flag and its value when
env.get("DOCKER_HOST") returns a non-empty value (e.g., assign host =
env.get("DOCKER_HOST") and if host: extend the args with
"--container-daemon-socket" and host), ensuring no empty-string flag is passed.

180-228: 🧹 Nitpick | 🔵 Trivial | ⚡ Quick win

Collapse duplicated act tests into a single parametrized test.

Replace the two near-identical tests with @pytest.mark.parametrize and move require_act() into a fixture. This reduces duplication and aligns with project guidelines.

Proposed refactor
+@pytest.fixture
+def act_ready() -> None:
+    """Skip test if act environment is unavailable."""
+    require_act()
+
+
 `@pytest.mark.act`
-def test_python_only_workflow_runs_with_shared_coverage_action(
-    copier: CopierFixture, tmp_path: Path
-) -> None:
-    """Validate the Python-only generated CI workflow through act."""
-    require_act()
-    project = copier.copy(
-        tmp_path / "act-pure",
-        project_name="ActPure",
-        package_name="act_pure",
-        use_rust=False,
-    )
-    prepare_git_repository(project)
-
-    code, logs = run_act(project, artifact_dir=tmp_path / "pure-artifacts")
-
-    assert (
-        "leynos/shared-actions/.github/actions/generate-coverage"
-        in (
-            project / ".github" / "workflows" / "ci.yml"
-        ).read_text()
-    ), "Python-only workflow should use the shared generate-coverage action"
-    assert_act_result(project, code, logs, use_rust=False)
-
-
-@pytest.mark.act
-def test_rust_extension_workflow_runs_with_shared_coverage_action(
-    copier: CopierFixture, tmp_path: Path
+@pytest.mark.parametrize(
+    ("name", "package", "use_rust", "artifact_subdir"),
+    [
+        ("ActPure", "act_pure", False, "pure-artifacts"),
+        ("ActRust", "act_rust", True, "rust-artifacts"),
+    ],
+)
+def test_workflow_runs_with_shared_coverage_action(
+    copier: CopierFixture,
+    tmp_path: Path,
+    act_ready: None,
+    name: str,
+    package: str,
+    use_rust: bool,
+    artifact_subdir: str,
 ) -> None:
-    """Validate the Rust-extension generated CI workflow through act."""
-    require_act()
+    """Validate the generated CI workflow through act."""
     project = copier.copy(
-        tmp_path / "act-rust",
-        project_name="ActRust",
-        package_name="act_rust",
-        use_rust=True,
+        tmp_path / package,
+        project_name=name,
+        package_name=package,
+        use_rust=use_rust,
     )
     prepare_git_repository(project)
-
-    code, logs = run_act(project, artifact_dir=tmp_path / "rust-artifacts")
+    code, logs = run_act(project, artifact_dir=tmp_path / artifact_subdir)
 
     workflow = (project / ".github" / "workflows" / "ci.yml").read_text()
-    assert (
-        "leynos/shared-actions/.github/actions/generate-coverage" in workflow
-    ), "Rust workflow should use the shared generate-coverage action"
-    assert (
-        "cargo-manifest: rust_extension/Cargo.toml" in workflow
-    ), "Rust workflow should pass the Rust extension manifest to coverage"
-    assert_act_result(project, code, logs, use_rust=True)
+    assert "leynos/shared-actions/.github/actions/generate-coverage" in workflow, \
+        "workflow should use the shared generate-coverage action"
+    if use_rust:
+        assert "cargo-manifest: rust_extension/Cargo.toml" in workflow, \
+            "Rust workflow should pass the Rust extension manifest to coverage"
+    assert_act_result(project, code, logs, use_rust=use_rust)

As per coding guidelines: "Use pytest fixtures for shared setup" and "Replace duplicate tests with @pytest.mark.parametrize".

🤖 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 `@tests/test_github_actions_integration.py` around lines 180 - 228, Collapse
the two tests test_python_only_workflow_runs_with_shared_coverage_action and
test_rust_extension_workflow_runs_with_shared_coverage_action into one
parametrized test (e.g.,
`@pytest.mark.parametrize`("use_rust,artifact_subdir,expected_manifest",
[(False,"pure-artifacts",None),(True,"rust-artifacts","cargo-manifest:
rust_extension/Cargo.toml")]) that calls copier.copy with use_rust, prepares the
git repo, runs run_act and asserts the shared action string is in the workflow
and, when use_rust is True, also asserts the expected_manifest is present, then
calls assert_act_result with use_rust; remove direct require_act() calls from
the test and instead create a small fixture that invokes require_act() (and is
used by the parametrized test) so shared setup lives in a fixture; keep
references to functions copier.copy, run_act, assert_act_result and the workflow
variable to locate where to change the code.
template/Makefile.jinja (1)

92-97: ⚠️ Potential issue | 🟡 Minor | ⚡ Quick win

Prefix PATH when invoking whitaker-installer after cargo install.

The whitaker-installer binary is installed to ~/.cargo/bin, which may not be on PATH immediately after cargo install. Prefix the invocation with the user binary paths to ensure the newly installed binary is found.

Proposed fix
 	  $(CARGO) install --locked \
 	    --git https://github.com/leynos/whitaker \
 	    --rev "$(WHITAKER_INSTALLER_REV)" \
 	    whitaker-installer; \
-	  whitaker-installer --cranelift; \
+	  PATH="$(USER_BIN_PATH):$(PATH)" whitaker-installer --cranelift; \
 	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 `@template/Makefile.jinja` around lines 92 - 97, The Makefile invokes
`whitaker-installer` immediately after `$(CARGO) install` but doesn't ensure the
user cargo bin dir is on PATH; update the invocation that currently reads
`whitaker-installer --cranelift` to run with the cargo bin directory prefixed
(e.g. use `PATH="$(CARGO_HOME)/bin:$$PATH" whitaker-installer --cranelift` or
call `$(CARGO_HOME)/bin/whitaker-installer --cranelift`) so the freshly
installed binary is found; modify the block containing the `$(CARGO) install
--locked ... whitaker-installer; \` and the subsequent `whitaker-installer
--cranelift; \` line accordingly.
🤖 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 `@template/Makefile.jinja`:
- Around line 50-51: The build-release Makefile target currently calls python
directly; change the recipe in the build-release target to invoke the virtualenv
wrapper variable UV by using "$(UV) run python -m build --sdist --wheel" so it
runs inside the same environment as the other targets (update the build-release
target to use $(UV) run rather than plain python).

---

Duplicate comments:
In `@template/Makefile.jinja`:
- Around line 92-97: The Makefile invokes `whitaker-installer` immediately after
`$(CARGO) install` but doesn't ensure the user cargo bin dir is on PATH; update
the invocation that currently reads `whitaker-installer --cranelift` to run with
the cargo bin directory prefixed (e.g. use `PATH="$(CARGO_HOME)/bin:$$PATH"
whitaker-installer --cranelift` or call `$(CARGO_HOME)/bin/whitaker-installer
--cranelift`) so the freshly installed binary is found; modify the block
containing the `$(CARGO) install --locked ... whitaker-installer; \` and the
subsequent `whitaker-installer --cranelift; \` line accordingly.

In `@tests/test_github_actions_integration.py`:
- Around line 67-68: The test currently always passes the
"--container-daemon-socket" flag with env.get("DOCKER_HOST", "") which can
result in an empty-string CLI arg; change the construction so you only append
the flag and its value when env.get("DOCKER_HOST") returns a non-empty value
(e.g., assign host = env.get("DOCKER_HOST") and if host: extend the args with
"--container-daemon-socket" and host), ensuring no empty-string flag is passed.
- Around line 180-228: Collapse the two tests
test_python_only_workflow_runs_with_shared_coverage_action and
test_rust_extension_workflow_runs_with_shared_coverage_action into one
parametrized test (e.g.,
`@pytest.mark.parametrize`("use_rust,artifact_subdir,expected_manifest",
[(False,"pure-artifacts",None),(True,"rust-artifacts","cargo-manifest:
rust_extension/Cargo.toml")]) that calls copier.copy with use_rust, prepares the
git repo, runs run_act and asserts the shared action string is in the workflow
and, when use_rust is True, also asserts the expected_manifest is present, then
calls assert_act_result with use_rust; remove direct require_act() calls from
the test and instead create a small fixture that invokes require_act() (and is
used by the parametrized test) so shared setup lives in a fixture; keep
references to functions copier.copy, run_act, assert_act_result and the workflow
variable to locate where to change the code.
🪄 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: Organization UI

Review profile: ASSERTIVE

Plan: Pro Plus

Run ID: abe067e6-3e8c-407d-9ed9-68b0995b87c3

📥 Commits

Reviewing files that changed from the base of the PR and between cf6d0c9 and beaed16.

📒 Files selected for processing (2)
  • template/Makefile.jinja
  • tests/test_github_actions_integration.py

Comment thread template/Makefile.jinja Outdated
@leynos
Copy link
Copy Markdown
Owner Author

leynos commented May 16, 2026

@coderabbitai Have the following now been resolved?

Annul any requirements that violate the en-GB-oxendict spelling (-ize / -yse / -our) conventions (for example a request to replace "normalize" with "normalise" or "artefact" with "artifact"), or where the requirement unnecessarily increases cyclomatic complexity.

Please address the comments from this code review:

## Overall Comments
- The Makefile uses `UV_CACHE_DIR=.uv-cache` and `UV_TOOL_DIR=.uv-tools` but the `clean` target does not remove these directories; consider adding them so `make clean` fully resets the project state.
- In `require_act`, the `docker` binary is required even when a podman socket is configured via `DOCKER_HOST`; if podman-only setups are intended to be supported, you might want to relax or adjust this check to allow a working container runtime without a `docker` CLI.

## Individual Comments

### Comment 1
<location path="tests/test_github_actions_integration.py" line_range="93-102" />
<code_context>
+def assert_ci_exercised_expected_steps(logs: str, *, use_rust: bool) -> None:
</code_context>
<issue_to_address>
**suggestion (testing):** Tighten the log matching in `assert_ci_exercised_expected_steps` to reduce the chance of false positives.

Right now, this helper treats the run as successful if any JSON log line’s `output` contains generic substrings like `"pytest"` or `"cargo"`, which risks false positives (e.g. those strings appearing in setup steps). Please tighten the matching by tying it to specific step names (e.g. the exact `name:` values for Python/Rust test steps) or by requiring both a matching step name and an `output` snippet, so these tests better track the intended CI steps and ignore incidental log noise.

Suggested implementation:

```python
        try:
            event = json.loads(line)
            step_name = (
                event.get("name")
                or event.get("step")  # some GitHub log formats use "step"
                or ""
            )
            output = event.get("output") or ""

            # Require both a specific step name and an identifying output snippet
            if step_name == "Run Python tests" and "pytest" in output:
                saw_python = True

            if step_name == "Run Rust tests" and ("cargo test" in output or "cargo nextest" in output):
                saw_rust = True

            if step_name == "Report coverage" and ("coverage" in output or "pytest-cov" in output):
                saw_coverage = True
        except json.JSONDecodeError:

```

To fully implement the tightened matching, you should:
1. Remove or adjust any existing logic *after* this `except json.JSONDecodeError:` block that sets `saw_python`, `saw_rust`, or `saw_coverage` based solely on generic substrings in `event["output"]` (e.g. checks like `"pytest" in output` or `"cargo" in output` without verifying the step name).
2. Align the step-name strings (`"Run Python tests"`, `"Run Rust tests"`, `"Report coverage"`) with the actual `name:` values used in your CI workflow YAML, or centralize them as constants if such constants already exist in the test module.
3. If your logs use different JSON keys for step names or output (e.g. `"step_name"`, `"message"`), adapt the `step_name`/`output` extraction accordingly.
</issue_to_address>

### Comment 2
<location path="template/docs/local-validation-of-github-actions-with-act-and-pytest.md" line_range="3" />
<code_context>
+# Local validation of GitHub Actions with act and pytest (black-box)
+
+This guide focuses on **pre-CI smoke/integration testing** of a workflow using
+`act` and `pytest`, treating the workflow as a **black box**. The assertions
+target artefacts, workspace side effects, and structured logs. Host-side
</code_context>
<issue_to_address>
**issue (review_instructions):** The acronym "CI" is used here without being expanded on first use.

Please expand "CI" on its first occurrence, for example "pre–Continuous Integration (CI) smoke/integration testing", and then use "CI" thereafter.

<details>
<summary>Review instructions:</summary>

**Path patterns:** `**/*.md`

**Instructions:**
Define uncommon acronyms on first use.

</details>
</issue_to_address>

### Comment 3
<location path="template/docs/local-validation-of-github-actions-with-act-and-pytest.md" line_range="15" />
<code_context>
+- Integration-test the **workflow** locally via `act`, from a `pytest` harness.
+- Assert on **artefacts**, **file outputs**, and **logs** (using `act --json`).
+- Treat results as pre-CI confidence; certify on GitHub runners for
+  permissions/OIDC parity.
+
+## Prerequisites
</code_context>
<issue_to_address>
**issue (review_instructions):** "OIDC" is introduced here without being expanded on first use.

Please expand "OIDC" the first time it appears, for example "OpenID Connect (OIDC) parity".

<details>
<summary>Review instructions:</summary>

**Path patterns:** `**/*.md`

**Instructions:**
Define uncommon acronyms on first use.

</details>
</issue_to_address>

### Comment 4
<location path="template/docs/local-validation-of-github-actions-with-act-and-pytest.md" line_range="164" />
<code_context>
+
+## Record -> replay -> verify (closing the loop)
+
+`cmd-mox` complements this harness when a workflow drives helper scripts that
+shell out to external CLIs. The tooling follows a record, replay, and verify
+loop:
</code_context>
<issue_to_address>
**issue (review_instructions):** The following line introduces "CLIs" without expanding the acronym on first use.

On the next line you refer to "external CLIs"; please expand this on first use, for example "external command-line interfaces (CLIs)".

<details>
<summary>Review instructions:</summary>

**Path patterns:** `**/*.md`

**Instructions:**
Define uncommon acronyms on first use.

</details>
</issue_to_address>

### Comment 5
<location path="template/docs/local-validation-of-github-actions-with-act-and-pytest.md" line_range="238" />
<code_context>
+
+1. **Local fast loop:** unit tests -> `act` black-box tests via `pytest`.
+2. **Authoritative CI:** run the same workflow on GitHub-hosted runners.
+3. **End-to-end (privileged paths):** GH-only with least-privilege tokens; gate
+   behind labels/paths.
+
</code_context>
<issue_to_address>
**issue (review_instructions):** "GH" is used as an abbreviation without being defined.

Please expand "GH" on first use, for example "GitHub (GH)-only", or avoid the abbreviation and spell out "GitHub" in full.

<details>
<summary>Review instructions:</summary>

**Path patterns:** `**/*.md`

**Instructions:**
Define uncommon acronyms on first use.

</details>
</issue_to_address>

@coderabbitai

This comment was marked as resolved.

Make the generated-file helper match its contract by passing an explicit UTF-8
encoding to `Path.read_text`.
@coderabbitai
Copy link
Copy Markdown

coderabbitai Bot commented May 16, 2026

Tip

For best results, initiate chat on the files or code changes.

🧩 Analysis chain

🏁 Script executed:

#!/bin/bash
echo "=== template/Makefile.jinja clean target ==="
rg -n "clean" template/Makefile.jinja -A5 -B2

echo ""
echo "=== require_act in tests/test_github_actions_integration.py ==="
rg -n "require_act\|docker\|podman" tests/test_github_actions_integration.py -A3 -B1

echo ""
echo "=== assert_ci_exercised_expected_steps ==="
rg -n "assert_ci_exercised_expected_steps\|saw_python\|saw_rust\|saw_coverage\|step_name\|json.loads" tests/test_github_actions_integration.py -A3 -B1

echo ""
echo "=== Acronym expansions in local-validation doc ==="
cat "template/docs/local-validation-of-github-actions-with-act-and-pytest.md" | head -250

Length of output: 8837


🏁 Script executed:

#!/bin/bash
echo "=== Does tests/test_github_actions_integration.py exist? ==="
fd "test_github_actions_integration.py"

echo ""
echo "=== Full content ==="
cat tests/test_github_actions_integration.py 2>/dev/null || echo "FILE NOT FOUND"

Length of output: 7800


@leynos All seven items are now resolved. Here is the breakdown:


Overall Comments

Item Status
clean target does not remove .uv-cache/.uv-tools ✅ Resolved — template/Makefile.jinja line 56 now includes both directories in the rm -rf invocation.
require_act requires docker even when a podman socket is configured ✅ Resolved — container_info_command() now probes docker first, then falls back to podman, and require_act delegates to it throughout.

Individual Comments

# Item Status
1 Tighten log matching in assert_ci_exercised_expected_steps ✅ Resolved — all three saw_* flags are now gated on both GENERATE_COVERAGE_STEP in step (step-name check) and a specific output substring (e.g. run_python.py, pytest -v, cargo nextest), eliminating the false-positive risk from generic substring matches. The approach consolidates checks under the "Test and Measure Coverage" step rather than per-step names, which is equally precise given the shared-actions workflow layout.
2 "CI" unexpanded on first use ✅ Resolved — "pre-Continuous Integration (CI) smoke/integration testing".
3 "OIDC" unexpanded on first use ✅ Resolved — "OpenID Connect (OIDC) parity".
4 "CLIs" unexpanded on first use ✅ Resolved — "external command-line interfaces (CLIs)".
5 "GH" used without definition ✅ Resolved — replaced with "GitHub-only" in full.

Note: The read_text() calls in tests/test_github_actions_integration.py (the two reads of ci.yml) still lack encoding="utf-8". This was flagged in the previous round of failed checks. Would you like to open a GitHub issue to track it, or address it in this PR?

coderabbitai[bot]

This comment was marked as resolved.

Collapse the Python-only and Rust workflow validation cases into one
parameterized test and move the act readiness check into shared pytest
fixture setup. Avoid passing an empty container socket argument to act
when DOCKER_HOST is unset.
coderabbitai[bot]

This comment was marked as resolved.

leynos added 2 commits May 16, 2026 22:48
Share the test Docker environment helper and canonicalise accepted unix
socket values before forwarding them to act.

Try both Docker and Podman runtime probes before skipping act validation,
and make generated `pyproject.toml` reads explicit UTF-8 reads.
Canonicalise the fallback Podman socket before exposing it through
`DOCKER_HOST`, and keep accepted sockets inside the current user's runtime
subtree.
coderabbitai[bot]

This comment was marked as resolved.

@coderabbitai
Copy link
Copy Markdown

coderabbitai Bot commented May 17, 2026

Tip

For best results, initiate chat on the files or code changes.

🧩 Analysis chain

🏁 Script executed:

#!/bin/bash
echo "=== read_text() calls in tests/test_template.py ==="
rg -n 'read_text' tests/test_template.py

echo ""
echo "=== read_text() calls in tests/test_github_actions_integration.py ==="
rg -n 'read_text' tests/test_github_actions_integration.py

echo ""
echo "=== docker_environment() in tests/utilities.py ==="
cat tests/utilities.py 2>/dev/null || echo "FILE NOT FOUND"

echo ""
echo "=== curl|bash in ci.yml.jinja ==="
rg -n 'curl' "template/.github/workflows/ci.yml.jinja"

echo ""
echo "=== GitHub issues mentioning curl or bash or security ==="
gh issue list --repo leynos/agent-template-python --state open --limit 20

Length of output: 3311


🏁 Script executed:

#!/bin/bash
echo "=== Lines 165-175 of tests/test_github_actions_integration.py ==="
sed -n '165,175p' tests/test_github_actions_integration.py

Length of output: 444


All encoding fixes and the DOCKER_HOST validation are resolved. The one outstanding item is the curl | bash pattern. No issue exists for it yet — create one now.


🏁 Script executed:

#!/bin/bash
gh issue create \
  --repo leynos/agent-template-python \
  --title "Replace curl-pipe-bash installer in generated CI workflow with a safer alternative" \
  --body "## Problem

\`template/.github/workflows/ci.yml.jinja\` (line 88) installs the CodeScene coverage tool by piping an unversioned, externally-hosted shell script directly into \`bash\`:

\`\`\`yaml
curl -fsSL https://downloads.codescene.io/enterprise/cli/install-cs-coverage-tool.sh | bash -s -- -y
\`\`\`

This pattern is a security and reproducibility concern: the script content is not pinned, not checksum-verified, and executes with full shell privileges. A compromised or altered upstream script would run undetected in every generated project's CI pipeline.

## Proposed alternatives

- Pin to a specific version/SHA and verify a published checksum before executing.
- Use a versioned GitHub Action provided by CodeScene if one exists.
- Download the script to a temporary file, verify its checksum, then execute.

## Context

Flagged during review of PR `#10` (https://github.com/leynos/agent-template-python/pull/10). Deferred from that PR as out of scope.

/cc `@leynos`" \
  --assignee leynos

Length of output: 58


@leynos All failed checks are now resolved. Here is the final status:

Check Status Notes
encoding="utf-8" on all read_text() calls (tests/test_template.py lines 24, 47, 72, 97) ✅ Resolved All calls carry encoding="utf-8"
encoding="utf-8" on read_text() in tests/test_github_actions_integration.py line 168 ✅ Resolved Confirmed on lines 168–169
DOCKER_HOST path validation in docker_environment() ✅ Resolved tests/utilities.py validates via _resolved_socket_from_docker_host, resolve(), and is_relative_to() guards
curl | bash in generated CI workflow ➡️ Tracked Out of scope for this PR; issue #11 opened — Replace curl-pipe-bash installer in generated CI workflow with a safer alternative

leynos added 2 commits May 17, 2026 11:50
Expand the public test helper and fixture docstrings into structured
NumPy-style documentation, including module-level usage notes.

Quote the generated import check as a Python script passed through
`shlex.quote` so package names are imported dynamically rather than
interpolated into shell code.
Run release builds through `uv run` so they use the generated project's managed
environment.

Prefix the user cargo bin directory when invoking `whitaker-installer` so a
freshly installed binary is available immediately.
@coderabbitai coderabbitai Bot added Roadmap and removed Issue labels May 17, 2026
Copy link
Copy Markdown

@coderabbitai coderabbitai Bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 1

🤖 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 `@template/Makefile.jinja`:
- Around line 103-105: Add a cargo-availability guard and call it before every
Rust-invoking recipe: define ensure_cargo (uses CARGO_AVAILABLE/CARGO) and
invoke it at the top of the fmt, check-fmt, and lint-rust targets so any
invocation of $(CARGO) or Rust steps (references: fmt target, check-fmt target,
lint-rust target, and the $(CARGO) symbol) first runs $(call ensure_cargo) to
fail fast with a clear diagnostic when cargo is missing.
🪄 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: Organization UI

Review profile: ASSERTIVE

Plan: Pro Plus

Run ID: 13926b66-f248-44a1-8f13-6bc86bc0a5da

📥 Commits

Reviewing files that changed from the base of the PR and between 3e7ac97 and 49a7e3c.

📒 Files selected for processing (1)
  • template/Makefile.jinja

Comment thread template/Makefile.jinja
Add a shared `ensure_cargo` recipe guard and run it before generated Rust
formatting and linting commands invoke `$(CARGO)`.
coderabbitai[bot]

This comment was marked as resolved.

Add a shared `ensure_uv` guard before generated targets invoke `$(UV)`, and
make virtualenv tool checks depend on the synced build target.
@leynos
Copy link
Copy Markdown
Owner Author

leynos commented May 17, 2026

@coderabbitai pause

Copy link
Copy Markdown

@coderabbitai coderabbitai Bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 1

🤖 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 `@template/Makefile.jinja`:
- Around line 171-173: The help target currently uses awk with FS=":" which
leaves prerequisites and the "##" marker in $$2; update the awk field separator
to FS=":.*##" so the second field contains only the description for targets like
build: .venv ## Build..., and trim leading whitespace from the description
(e.g., with gsub) before printing; locate and modify the help target block using
the "help" recipe in the Makefile.jinja to implement these changes.
🪄 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: Organization UI

Review profile: ASSERTIVE

Plan: Pro Plus

Run ID: a2c91af8-d06e-42fc-a0d0-1c2b4c1d3a5f

📥 Commits

Reviewing files that changed from the base of the PR and between aa6d7bb and d4985c8.

📒 Files selected for processing (1)
  • template/Makefile.jinja

Comment thread template/Makefile.jinja Outdated
@coderabbitai
Copy link
Copy Markdown

coderabbitai Bot commented May 17, 2026

✅ Actions performed

Reviews paused.

Parse generated help descriptions after the `##` marker so prerequisite text is
not included in help output.

Add syrupy to the parent test setup and snapshot the generated Python-only
`make help` output to lock the formatting contract.
@leynos leynos merged commit 2270b97 into main May 17, 2026
2 checks passed
@leynos leynos deleted the import-lints branch May 17, 2026 13:51
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant