Skip to content

Add release workflow actions migrated from netsuke#219

Merged
leynos merged 36 commits intomainfrom
import-shared-actions-from-netsuke
Dec 29, 2025
Merged

Add release workflow actions migrated from netsuke#219
leynos merged 36 commits intomainfrom
import-shared-actions-from-netsuke

Conversation

@leynos
Copy link
Copy Markdown
Owner

@leynos leynos commented Dec 26, 2025

Migrate four reusable GitHub Actions from leynos/netsuke:

  • export-cargo-metadata: Extract Cargo.toml metadata as outputs/env vars
  • determine-release-modes: Normalize event payloads into release flags
  • upload-release-assets: Upload staged artefacts to GitHub releases
  • stage-release-artefacts: Stage artefacts using TOML configuration

Also adds shared cargo_utils.py for Cargo.toml parsing, now used by both export-cargo-metadata and ensure-cargo-version actions.

All actions include comprehensive tests (147 new tests total) and follow the established patterns: PEP 723 headers, cyclopts CLI, and plumbum for subprocess execution.

🤖 Generated with Claude Code

Summary by Sourcery

Migrate shared Cargo manifest and boolean parsing utilities, and add new reusable GitHub Actions for release metadata, staging, mode determination, and asset upload.

New Features:

  • Introduce reusable export-cargo-metadata action to expose Cargo.toml metadata as GitHub Actions outputs and environment variables.
  • Add determine-release-modes action to normalise workflow events into dry-run, publish, and artefact-upload flags for release workflows.
  • Add stage-release-artefacts action and staging helper library to configure, stage, and checksum release artefacts via TOML configuration.
  • Add upload-release-assets action and script to discover, validate, and upload staged release artefacts to GitHub releases via the gh CLI.

Enhancements:

  • Factor out shared cargo_utils and bool_utils modules and update existing ensure-cargo-version action to consume them.
  • Standardise test harness imports using syspath_hack and expand test coverage across the new actions and helpers.
  • Document local GitHub Actions validation workflow with act and pytest, and add README/changelogs for the new reusable actions.

Tests:

  • Add comprehensive unit tests for cargo_utils, bool_utils, and the new determine-release-modes, export-cargo-metadata, stage-release-artefacts, and upload-release-assets actions.

Migrate four reusable GitHub Actions from leynos/netsuke:

- export-cargo-metadata: Extract Cargo.toml metadata as outputs/env vars
- determine-release-modes: Normalize event payloads into release flags
- upload-release-assets: Upload staged artefacts to GitHub releases
- stage-release-artefacts: Stage artefacts using TOML configuration

Also adds shared cargo_utils.py for Cargo.toml parsing, now used by both
export-cargo-metadata and ensure-cargo-version actions.

All actions include comprehensive tests (147 new tests total) and follow
the established patterns: PEP 723 headers, cyclopts CLI, and plumbum for
subprocess execution.

🤖 Generated with [Claude Code](https://claude.com/claude-code)

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
@coderabbitai
Copy link
Copy Markdown
Contributor

coderabbitai Bot commented Dec 26, 2025

Note

Other AI code review bot(s) detected

CodeRabbit has detected other AI code review bot(s) in this pull request and will avoid duplicating their findings in the review comments. This may lead to a less comprehensive review.

Summary by CodeRabbit

Release Notes

  • New Features

    • Added four new GitHub Actions: determine-release-modes for automated workflow decision flags, export-cargo-metadata for manifest data extraction, stage-release-artefacts for release asset preparation, and upload-release-assets for managing release uploads with dry-run support.
    • Added utility modules for manifest handling and boolean input coercion.
  • Documentation

    • Added comprehensive guides and examples for all new actions.
  • Tests

    • Added extensive test coverage including end-to-end workflow validation.

✏️ Tip: You can customize this high-level summary in your review settings.

Walkthrough

Summarise added composite GitHub Actions for release workflows (determine-release-modes, export-cargo-metadata, stage-release-artefacts, upload-release-assets), introduce cargo_utils and bool_utils helpers, implement a stage_common staging library, refactor ensure-cargo-version to reuse cargo_utils, and add tests, behavioural workflows and documentation for local validation.

Changes

Cohort / File(s) Summary
determine-release-modes action
​.github/actions/determine-release-modes/CHANGELOG.md, ​.github/actions/determine-release-modes/README.md, ​.github/actions/determine-release-modes/action.yml, ​.github/actions/determine-release-modes/scripts/determine_release_modes.py, ​.github/actions/determine-release-modes/tests/test_determine_release_modes.py
Add composite action and script that normalises GitHub event payloads into outputs dry-run, should-publish, should-upload-workflow-artifacts; support push, workflow_call, pull_request; include docs and tests.
export-cargo-metadata action
​.github/actions/export-cargo-metadata/CHANGELOG.md, ​.github/actions/export-cargo-metadata/README.md, ​.github/actions/export-cargo-metadata/action.yml, ​.github/actions/export-cargo-metadata/scripts/read_manifest.py, ​.github/actions/export-cargo-metadata/tests/test_read_manifest.py
Add composite action and script to extract Cargo.toml fields (name, version, bin-name, description), handle workspace inheritance, export to GITHUB_OUTPUT and optionally GITHUB_ENV; include docs and tests.
stage-release-artefacts action & library
​.github/actions/stage-release-artefacts/CHANGELOG.md, ​.github/actions/stage-release-artefacts/README.md, ​.github/actions/stage-release-artefacts/action.yml, ​.github/actions/stage-release-artefacts/scripts/stage.py, ​.github/actions/stage-release-artefacts/scripts/stage_common/*, ​.github/actions/stage-release-artefacts/tests/test_stage.py
Add composite action, CLI entrypoint and staging library: TOML config loader, ArtefactConfig/StagingConfig dataclasses, template rendering, glob/path resolution, staging pipeline with checksum sidecars, output assembly and writer, Windows-path normalisation, reserved-key validation and tests.
upload-release-assets action
​.github/actions/upload-release-assets/CHANGELOG.md, ​.github/actions/upload-release-assets/README.md, ​.github/actions/upload-release-assets/action.yml, ​.github/actions/upload-release-assets/scripts/upload_release_assets.py, ​.github/actions/upload-release-assets/tests/test_upload_release_assets.py
Add composite action and script to discover, validate and upload staged artefacts to a GitHub release via gh, with dry-run, clobber and nested-name handling; include docs and tests.
ensure-cargo-version refactor
​.github/actions/ensure-cargo-version/scripts/ensure_cargo_version.py, ​.github/actions/ensure-cargo-version/scripts/tests/test_ensure_cargo_version.py
Replace in‑script manifest parsing with cargo_utils APIs (read_manifest, find_workspace_root, get_workspace_version), use coerce_bool_strict, propagate ManifestError, ensure GITHUB_OUTPUT parent dirs are created; update tests to use syspath helper.
cargo_utils utility
cargo_utils.py, tests/test_cargo_utils.py
Add Cargo.toml utilities: ManifestError, read_manifest, get_package_field, get_bin_name, find_workspace_root, get_workspace_version, resolve_version and tests.
bool_utils utility
bool_utils.py, tests/test_bool_utils.py
Add boolean coercion helpers coerce_bool and coerce_bool_strict with unit tests.
stage_common submodules
​.github/actions/stage-release-artefacts/scripts/stage_common/config.py, .../environment.py, .../errors.py, .../output.py, .../pipeline.py, .../resolution.py, .../__init__.py
Provide ArtefactConfig and StagingConfig dataclasses, require_env_path, StageError, GitHub-output assembly and writer, core staging pipeline (templating, glob resolution, copying + checksum), resolution helpers and public exports.
workflows & behavioural tests
​.github/workflows/test-*.yml, tests/workflows/*, tests/workflows/fixtures/*
Add workflows exercising new actions and guarded behavioural tests using act; provide fixtures and a run_act helper; include tests that assert outputs and artefact side effects.
misc syspath & test updates
​.github/actions/generate-coverage/scripts/tests/test_run_rust_windows.py, ​.github/actions/ensure-cargo-version/scripts/tests/test_ensure_cargo_version.py
Replace ad‑hoc sys.path manipulation with prepend_to_syspath helper; remove deprecated coerce_bool parametric tests; add test ensuring GITHUB_OUTPUT parent dirs are created.
docs & README
docs/local-validation-of-github-actions-with-act-and-pytest.md, README.md
Add local validation guide for workflows and update root README action table to include and reorganise the new actions.

Sequence Diagram(s)

sequenceDiagram
  autonumber
  rect rgb(240,248,255)
    participant WF as Workflow
    participant Action as Composite Action
    participant Script as Action Script
    participant FS as Filesystem
    participant GOut as GITHUB_OUTPUT
    participant GH as gh CLI
  end

  WF->>Action: invoke (inputs, event)
  Action->>Script: execute (env: INPUT_*)
  Script->>FS: read event / manifest / config
  Script->>FS: resolve artefact paths & templates
  alt determine-release-modes
    Script->>GOut: write outputs (dry-run, should-publish, should-upload-workflow-artifacts)
    Script-->>Action: exit 0
  else stage-release-artefacts then upload-release-assets
    Script->>FS: copy staged artefacts, write checksum sidecars
    Script->>GOut: write staged outputs (staged-files, artefact-map, checksum-map)
    Script->>GH: invoke gh release upload (dry-run optional)
    GH-->>Script: respond with status
    Script->>GOut: write upload results (uploaded-count, upload-error)
    Script-->>Action: exit with status
  end
Loading

Estimated code review effort

🎯 4 (Complex) | ⏱️ ~60 minutes

Poem

Stage, checksum, publish or pause,
Cargo names and workflow laws,
Scripts that read and scripts that write,
Dry‑run guards and uploads flight,
Let CI hum and artefacts soar 🚀

Pre-merge checks and finishing touches

✅ Passed checks (3 passed)
Check name Status Explanation
Title check ✅ Passed The title accurately reflects the main objective: migrating four release workflow actions from netsuke, which is the primary changeset focus.
Description check ✅ Passed The description comprehensively covers the changeset, detailing the four migrated actions, shared utilities, test coverage, and implementation patterns used.
Docstring Coverage ✅ Passed Docstring coverage is 98.31% which is sufficient. The required threshold is 80.00%.
✨ Finishing touches
  • 📝 Generate docstrings
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Post copyable unit tests in a comment
  • Commit unit tests in branch import-shared-actions-from-netsuke

📜 Recent review details

Configuration used: Organization UI

Review profile: ASSERTIVE

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between c5fa1f1 and 8c5453c.

📒 Files selected for processing (1)
  • tests/workflows/test_action_behaviors.py
🧰 Additional context used
📓 Path-based instructions (3)
{.github/actions/**/src/**/*.{ts,tsx,js,sh},.rules/**/*.py,**/*.py}

📄 CodeRabbit inference engine (AGENTS.md)

After modifying any Python code or GitHub Action logic, ensure make check-fmt, make typecheck, make lint, and make test complete successfully before requesting review

Files:

  • tests/workflows/test_action_behaviors.py
**/*.py

📄 CodeRabbit inference engine (AGENTS.md)

For Python development in actions, follow guidelines in .rules/ directory including Python 3.13 style conventions, context managers, generators, project configuration, exception design, return patterns, and typing

**/*.py: Use snake_case.py for Python file names, named to reflect contents (e.g., http_client.py, task_queue.py)
Use PascalCase for class names
Use snake_case for variable and function names
Use UPPER_SNAKE_CASE for module-level constants
Prefix private or internal helpers with a single underscore (_)
Enable and maintain full static type coverage using Pyright type-checking. Use typing everywhere
Use TypedDict or dataclass for structured data, preferring @dataclass(slots=True) for internal-only usage
Avoid Any type annotation. Prefer precise types (TypeVar, Protocol, Literal, Union) and use typing.cast[...] only when necessary with justification. Use object for unknown-but-opaque values
Be explicit with return type annotations. Use -> None, -> str, etc., for all public functions and class methods
Favour immutability. Prefer tuples to lists, and types.MappingProxyType for read-only mappings
Avoid side effects at import time. Modules should not modify global state or perform actions on import
Use Ruff for formatting. Let Ruff handle whitespace and formatting entirely—do not override
Document public functions, classes, and modules using NumPy docstring format
Use inline comments to explain tricky or non-obvious code logic and decisions

**/*.py: Use context managers to encapsulate setup and teardown logic cleanly and safely, reducing the risk of forgetting to release resources (files, locks, connections, etc.) and simplifying error handling
Use @contextlib.contextmanager decorator for straightforward procedural setup/teardown with linear control flow and no persistent state
Implement __enter__ and __exit__ methods in a class-based context manager when internal state or lifecycle logic spans methods, or when you need...

Files:

  • tests/workflows/test_action_behaviors.py

⚙️ CodeRabbit configuration file

**/*.py: - Keep C90 / mccabe complexity ≤ 9

  • Follow single responsibility and CQRS (command/query segregation)
  • Prefer structural pattern matching to
  • Prefer structural pattern matching over isinstance() or imperative decomposition.
  • Docstrings must follow the numpy style guide. Use a single-line summary for private functions and methods, and full structured docs for all public interfaces.
  • Move conditionals with >2 branches to predicate/helper functions
  • Avoid eval, exec, pickle, monkey-patching, ctypes, unsafe shell
  • Every module must begin with a triple-quoted docstring explaining its purpose, utility, and usage, including example calls if appropriate.
  • Comments and docs must follow en-GB-oxendict (-ize / -yse / -our) spelling and grammar
  • Lint suppressions:
    • Blanket # noqa, file-level skips, and categories are forbidden
    • Only narrow in-line disables (# noqa: XYZ) are permitted, and must be accompanied by FIXME: or a ticket link, and used only as a last resort.
  • Use pytest fixtures for shared setup (conftest.py or fixtures/)
  • Replace duplicate tests with @pytest.mark.parametrize
  • Prefer pytest-mock or unittest.mock for stubs/mocks
  • Use assert …, "message" over bare asserts
  • Reflect all API/behaviour changes in docs/ and update roadmap on completion
  • Files must not exceed 400 logical lines:
    • Decompose large modules into subpackages
    • Split large match/case or dispatch tables by domain and collocate with targets if appropriate
    • Move bulky data (fixtures, templates) to external files for parsing at runtime
  • Mutable defaults and shadowed built-ins are forbidden
  • All code must have clear type hints using modern style (A | B, list[str], class Foo[A]:, type Bar = int, etc.), with ABC imports drawn from the correct stdlib module.
  • All path manipulation must be performed using pathlib for cross platform safety. Do not use string manipulation for extraction of path components or filename ele...

Files:

  • tests/workflows/test_action_behaviors.py
**/*test*.py

📄 CodeRabbit inference engine (.rules/python-00.md)

**/*test*.py: Use pytest idioms. Prefer fixtures to setup/teardown methods. Parametrize broadly. Avoid unnecessary mocks
Group related tests using class with method names prefixed by test_
Write tests from a user's perspective. Test public behaviour, not internals
Avoid mocking too much. Prefer test doubles only for external services or non-deterministic behaviours

In tests, use pytest.raises() with specific exception types and optional regex matchers to constrain expected exceptions, avoiding overly broad exception assertions (B017)

Files:

  • tests/workflows/test_action_behaviors.py
🧠 Learnings (1)
📚 Learning: 2025-12-27T17:51:29.917Z
Learnt from: leynos
Repo: leynos/shared-actions PR: 219
File: cargo_utils.py:40-41
Timestamp: 2025-12-27T17:51:29.917Z
Learning: In Python files that have 'from __future__ import annotations' (which makes type annotations be evaluated as strings), avoid creating runtime references to types just by annotating attributes. For types used only in annotations or docstrings, import them under a TYPE_CHECKING guard instead of at module import time. Do runtime imports only for actual usage (e.g., Path(...) construction, isinstance checks, classmethod calls, or default values that reference the type). For cargo_utils.py in leynos/shared-actions, ensure runtime behavior is not affected by type-only imports and prefer 'if TYPE_CHECKING' or local imports inside functions where the type is needed at runtime.

Applied to files:

  • tests/workflows/test_action_behaviors.py
🧬 Code graph analysis (1)
tests/workflows/test_action_behaviors.py (1)
tests/workflows/conftest.py (2)
  • ActConfig (56-62)
  • run_act (65-123)
🔍 Remote MCP

Let me try searching for context about the original netsuke project and the GitHub Actions patterns being used:Based on my search for relevant context to this pull request, here's the key additional information for PR review:

GitHub Actions Architecture & Best Practices

Composite actions allow you to collect a series of workflow job steps into a single action which you can then run as a single job step in multiple workflows. This PR introduces four well-structured composite actions following this pattern.

Key architectural points from the PR:

  • It's a good practice to store them next to the GitHub Workflow's .github/workflows directory in .github/actions, which this PR follows correctly with all four new actions in .github/actions/.
  • The using: "composite" field signals to GitHub Actions that the action being defined is not a traditional Docker or JavaScript action but rather a composite of multiple steps, allowing the action to bundle several commands or even other actions into a single, reusable unit.
  • The main difference is that a composite action's action.yml → runs property contains a list of steps to run as opposed to a program to execute.

Testing with act (Local Validation)

Rather than having to commit/push every time you want to test out the changes you are making to your .github/workflows/ files, you can use act to run the actions locally, with the environment variables and filesystem all configured to match what GitHub provides. The PR includes test fixtures and workflow test infrastructure using act, which aligns with modern GitHub Actions development practices.

Key Considerations for This Migration

  1. Input/Output Handling: To get input values as environment variables, they must be specified under the env key because inputs are not automatically provided as environment variables when using Composite Actions, unlike Workflows. The PR correctly implements this pattern across all actions.

  2. Composite Action Limitations: These actions appropriately use bash scripts for execution and don't attempt to chain other actions (which composite actions have limited support for per PR review feedback).

  3. Testing Coverage: The PR includes comprehensive unit tests and behavioral tests using act to validate workflow execution locally, following current best practices for CI/CD automation.

🔇 Additional comments (1)
tests/workflows/test_action_behaviors.py (1)

1-199: Excellent implementation—all previous feedback addressed.

The behavioural test suite is well-structured and follows all coding guidelines:

  • en-GB spelling throughout (Behavioural, behaviour, artefacts)
  • TYPE_CHECKING guard used correctly for Path import
  • NumPy-style docstrings for all functions and classes
  • Parametrized tests consolidate duplicate test logic
  • Strengthened regex assertions with _assert_log_patterns helper for consistency
  • All functions have proper type annotations
  • Low complexity throughout
  • Clear, descriptive error messages

The test coverage appropriately validates end-to-end behaviour for all four composite actions (determine-release-modes, export-cargo-metadata, stage-release-artefacts, upload-release-assets) using the act test harness.


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

Copy link
Copy Markdown
Contributor

@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

codescene-delta-analysis[bot]

This comment was marked as outdated.

Address CodeScene feedback on code health:

- Refactor cargo_utils.py resolve_version function by extracting helpers:
  _require_package_table, _is_workspace_inherited, _resolve_inherited_version,
  and _require_version_string. Reduces cyclomatic complexity from 12 to ~3.

- Refactor determine_release_modes.py by extracting _determine_dry_run and
  _determine_should_publish helpers. Reduces complexity from 9 to ~4.

- Refactor read_manifest.py main function by extracting _resolve_manifest_path,
  _validate_export_flag, _load_manifest, _export_field, and _process_fields.
  Reduces complexity from 15 to ~4.

- Fix Bumpy Road in resolution.py _resolve_glob_pattern by extracting
  _parts_to_pattern, _resolve_absolute_glob, and _resolve_windows_glob.

- Fix Bumpy Road in output.py write_github_output by extracting
  _format_list_output and _format_scalar_output helpers.

- Reduce test duplication in test_cargo_utils.py TestGetWorkspaceVersion
  using pytest.mark.parametrize.

- Reduce test duplication in test_read_manifest.py by consolidating 7
  similar _extract_field tests into a single parametrized test with
  _load_and_extract helper.

🤖 Generated with [Claude Code](https://claude.com/claude-code)

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
codescene-delta-analysis[bot]

This comment was marked as outdated.

- Fix EM102 lint errors in cargo_utils.py by assigning f-strings to
  variables before raising exceptions

- Fix staged_files output format in output.py to return list[str]
  instead of newline-joined string, ensuring proper heredoc output
  that matches the README promise of newline-separated list

- Add type validation in config.py for artefact 'required' (must be
  bool) and 'alternatives' (must be list[str]) fields, preventing
  silent iteration over string characters if TOML is malformed

- Add unknown field validation in read_manifest.py with warning when
  unsupported fields are requested, making typos visible in workflow
  logs instead of silently returning no value

🤖 Generated with [Claude Code](https://claude.com/claude-code)

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
codescene-delta-analysis[bot]

This comment was marked as outdated.

@leynos
Copy link
Copy Markdown
Owner Author

leynos commented Dec 26, 2025

@coderabbitai Please suggest a fix for this issue and supply a prompt for an AI coding agent to enable it to apply the fix:

.github/actions/stage-release-artefacts/scripts/stage_common/config.py

Comment on lines +170 to +221

def _make_artefacts(
    common: dict[str, typ.Any], target_cfg: dict[str, typ.Any], config_path: Path
) -> list[ArtefactConfig]:
    """Build list of ArtefactConfig from common and target sections."""
    entries = [*common.get("artefacts", []), *target_cfg.get("artefacts", [])]
    if not entries:
        msg = "No artefacts configured to stage."
        raise StageError(msg)
    artefacts: list[ArtefactConfig] = []
    for index, entry in enumerate(entries, start=1):
        source = entry.get("source")
        if not isinstance(source, str) or not source:
            msg = (
                "Missing required artefact key 'source' "
                f"in entry #{index} of {config_path}"
            )
            raise StageError(msg)

        required = entry.get("required", True)
        if not isinstance(required, bool):
            msg = (
                f"Artefact 'required' must be a boolean, got {type(required).__name__} "
                f"in entry #{index} of {config_path}"
            )
            raise StageError(msg)

        alternatives = entry.get("alternatives", [])
        if not isinstance(alternatives, list):
            alt_type = type(alternatives).__name__
            msg = (
                f"Artefact 'alternatives' must be a list, got {alt_type} "
                f"in entry #{index} of {config_path}"
            )
            raise StageError(msg)
        for alt_idx, alt in enumerate(alternatives):
            if not isinstance(alt, str):
                msg = (
                    f"Artefact alternatives[{alt_idx}] must be a string, "
                    f"got {type(alt).__name__} in entry #{index} of {config_path}"
                )
                raise StageError(msg)

        artefacts.append(
            ArtefactConfig(
                source=source,
                required=required,
                output=entry.get("output"),
                destination=entry.get("destination"),
                alternatives=alternatives,
            )
        )
    return artefacts

❌ New issue: Bumpy Road Ahead
_make_artefacts has 2 blocks with nested conditional logic. Any nesting of 2 or deeper is considered. Threshold is 2 blocks per function

- Extract validation helpers from _make_artefacts in config.py to fix
  Bumpy Road: _validate_source, _validate_required, _validate_alternatives,
  _parse_artefact_entry
- Extract _extract_first_bin_name from get_bin_name in cargo_utils.py
  to reduce module complexity below threshold
- Add _setup_github_env helper in test_read_manifest.py to reduce
  duplication across test_main_* functions

🤖 Generated with [Claude Code](https://claude.com/claude-code)

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
@coderabbitai

This comment was marked as resolved.

codescene-delta-analysis[bot]

This comment was marked as outdated.

@leynos
Copy link
Copy Markdown
Owner Author

leynos commented Dec 26, 2025

@coderabbitai Please suggest a fix for this issue and supply a prompt for an AI coding agent to enable it to apply the fix:

.github/actions/stage-release-artefacts/scripts/stage_common/config.py

Comment on lines +170 to +181

def _validate_source(
    entry: dict[str, typ.Any], index: int, config_path: Path
) -> str:
    """Validate and extract the source field from an artefact entry."""
    source = entry.get("source")
    if not isinstance(source, str) or not source:
        msg = (
            "Missing required artefact key 'source' "
            f"in entry #{index} of {config_path}"
        )
        raise StageError(msg)
    return source

❌ New issue: Code Duplication
The module contains 2 functions with similar structure: _validate_required,_validate_source

@coderabbitai

This comment was marked as resolved.

- Add _validate_field_type[T] generic function using Python 3.12+ type
  parameter syntax to consolidate type-checking logic
- Simplify _validate_source to 4 lines using the helper with
  allow_empty_str=False for required non-empty strings
- Simplify _validate_required to 4 lines using the helper for bool types
- Preserves all existing error messages exactly (including "boolean"
  instead of "bool" for bool type mismatches)

🤖 Generated with [Claude Code](https://claude.com/claude-code)

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
codescene-delta-analysis[bot]

This comment was marked as outdated.

@leynos
Copy link
Copy Markdown
Owner Author

leynos commented Dec 26, 2025

@coderabbitai Please suggest a fix for this issue and supply a prompt for an AI coding agent to enable it to apply the fix:

.github/actions/stage-release-artefacts/scripts/stage_common/config.py

Comment on lines +202 to +208

def _validate_source(
    entry: dict[str, typ.Any], index: int, config_path: Path
) -> str:
    """Validate and extract the source field from an artefact entry."""
    return _validate_field_type(
        entry.get("source"), "source", str, index, config_path, allow_empty_str=False
    )

❌ New issue: Bumpy Road Ahead
_validate_source has 2 blocks with nested conditional logic. Any nesting of 2 or deeper is considered. Threshold is 2 blocks per function

@coderabbitai

This comment was marked as resolved.

🤖 Generated with [Claude Code](https://claude.com/claude-code)

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
codescene-delta-analysis[bot]

This comment was marked as outdated.

@leynos
Copy link
Copy Markdown
Owner Author

leynos commented Dec 26, 2025

@coderabbitai Please suggest a fix for this issue and supply a prompt for an AI coding agent to enable it to apply the fix:

.github/actions/stage-release-artefacts/scripts/stage_common/config.py

Comment on lines +202 to +206

def _validate_source(entry: dict[str, typ.Any], index: int, config_path: Path) -> str:
    """Validate and extract the source field from an artefact entry."""
    return _validate_field_type(
        entry.get("source"), "source", str, index, config_path, allow_empty_str=False
    )

❌ New issue: Bumpy Road Ahead
_validate_source has 2 blocks with nested conditional logic. Any nesting of 2 or deeper is considered. Threshold is 2 blocks per function

@coderabbitai

This comment was marked as resolved.

Eliminate all nested if statements by using three sequential top-level
conditionals:
1. Valid non-empty string case - returns immediately
2. Invalid non-empty string case - raises error (only executes if #1 didn't return)
3. General type mismatch - unchanged

Extract `is_nonempty_str` variable to keep line length within 88 chars.

🤖 Generated with [Claude Code](https://claude.com/claude-code)

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
@leynos leynos force-pushed the import-shared-actions-from-netsuke branch from 4de6e0d to 1413eeb Compare December 26, 2025 23:13
codescene-delta-analysis[bot]

This comment was marked as outdated.

@leynos
Copy link
Copy Markdown
Owner Author

leynos commented Dec 26, 2025

@coderabbitai Please suggest a fix for this issue and supply a prompt for an AI coding agent to enable it to apply the fix:

.github/actions/stage-release-artefacts/scripts/stage_common/config.py

Comment on file

    For strings with ``allow_empty_str=False``, also checks the value is non-empty.
    """
    is_nonempty_str = isinstance(value, str) and value
    if expected_type is str and not allow_empty_str and is_nonempty_str:

❌ New issue: Complex Conditional
_validate_source has 1 complex conditionals with 2 branches, threshold = 2

@coderabbitai

This comment was marked as resolved.

Add dedicated helper for string validation with simple single-condition
checks:
- Type check: isinstance(value, str) raises "must be a string" error
- Empty check: not allow_empty and not value raises "Missing required"

Simplify _validate_field_type to delegate string validation entirely,
eliminating the complex 3-way AND conditional.

🤖 Generated with [Claude Code](https://claude.com/claude-code)

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
codescene-delta-analysis[bot]

This comment was marked as outdated.

@leynos
Copy link
Copy Markdown
Owner Author

leynos commented Dec 26, 2025

@coderabbitai Please suggest a fix for this issue and supply a prompt for an AI coding agent to enable it to apply the fix:

.github/actions/stage-release-artefacts/scripts/stage_common/config.py

Comment on lines +170 to +196

def _validate_string_field(
    value: object,
    field_name: str,
    index: int,
    config_path: Path,
    *,
    allow_empty: bool,
) -> str:
    """Validate a string field value.

    When ``allow_empty=False``, also checks the string is non-empty.
    """
    if not isinstance(value, str):
        msg = (
            f"Artefact '{field_name}' must be a string, "
            f"got {type(value).__name__} in entry #{index} of {config_path}"
        )
        raise StageError(msg)

    if not allow_empty and not value:
        msg = (
            f"Missing required artefact key '{field_name}' "
            f"in entry #{index} of {config_path}"
        )
        raise StageError(msg)

    return value

❌ New issue: Excess Number of Function Arguments
_validate_string_field has 5 arguments, max arguments = 4

Replace inline boolean coercion logic with the shared coerce_bool
helper from bool_utils for consistency with the rest of the codebase.

🤖 Generated with [Claude Code](https://claude.com/claude-code)

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
@coderabbitai

This comment was marked as resolved.

codescene-delta-analysis[bot]

This comment was marked as outdated.

Copy link
Copy Markdown
Contributor

@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

📜 Review details

Configuration used: Organization UI

Review profile: ASSERTIVE

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between 97faede and a494d16.

📒 Files selected for processing (1)
  • tests/workflows/conftest.py
🧰 Additional context used
📓 Path-based instructions (3)
{.github/actions/**/src/**/*.{ts,tsx,js,sh},.rules/**/*.py,**/*.py}

📄 CodeRabbit inference engine (AGENTS.md)

After modifying any Python code or GitHub Action logic, ensure make check-fmt, make typecheck, make lint, and make test complete successfully before requesting review

Files:

  • tests/workflows/conftest.py
**/*.py

📄 CodeRabbit inference engine (AGENTS.md)

For Python development in actions, follow guidelines in .rules/ directory including Python 3.13 style conventions, context managers, generators, project configuration, exception design, return patterns, and typing

**/*.py: Use snake_case.py for Python file names, named to reflect contents (e.g., http_client.py, task_queue.py)
Use PascalCase for class names
Use snake_case for variable and function names
Use UPPER_SNAKE_CASE for module-level constants
Prefix private or internal helpers with a single underscore (_)
Enable and maintain full static type coverage using Pyright type-checking. Use typing everywhere
Use TypedDict or dataclass for structured data, preferring @dataclass(slots=True) for internal-only usage
Avoid Any type annotation. Prefer precise types (TypeVar, Protocol, Literal, Union) and use typing.cast[...] only when necessary with justification. Use object for unknown-but-opaque values
Be explicit with return type annotations. Use -> None, -> str, etc., for all public functions and class methods
Favour immutability. Prefer tuples to lists, and types.MappingProxyType for read-only mappings
Avoid side effects at import time. Modules should not modify global state or perform actions on import
Use Ruff for formatting. Let Ruff handle whitespace and formatting entirely—do not override
Document public functions, classes, and modules using NumPy docstring format
Use inline comments to explain tricky or non-obvious code logic and decisions

**/*.py: Use context managers to encapsulate setup and teardown logic cleanly and safely, reducing the risk of forgetting to release resources (files, locks, connections, etc.) and simplifying error handling
Use @contextlib.contextmanager decorator for straightforward procedural setup/teardown with linear control flow and no persistent state
Implement __enter__ and __exit__ methods in a class-based context manager when internal state or lifecycle logic spans methods, or when you need...

Files:

  • tests/workflows/conftest.py

⚙️ CodeRabbit configuration file

**/*.py: - Keep C90 / mccabe complexity ≤ 9

  • Follow single responsibility and CQRS (command/query segregation)
  • Prefer structural pattern matching to
  • Prefer structural pattern matching over isinstance() or imperative decomposition.
  • Docstrings must follow the numpy style guide. Use a single-line summary for private functions and methods, and full structured docs for all public interfaces.
  • Move conditionals with >2 branches to predicate/helper functions
  • Avoid eval, exec, pickle, monkey-patching, ctypes, unsafe shell
  • Every module must begin with a triple-quoted docstring explaining its purpose, utility, and usage, including example calls if appropriate.
  • Comments and docs must follow en-GB-oxendict (-ize / -yse / -our) spelling and grammar
  • Lint suppressions:
    • Blanket # noqa, file-level skips, and categories are forbidden
    • Only narrow in-line disables (# noqa: XYZ) are permitted, and must be accompanied by FIXME: or a ticket link, and used only as a last resort.
  • Use pytest fixtures for shared setup (conftest.py or fixtures/)
  • Replace duplicate tests with @pytest.mark.parametrize
  • Prefer pytest-mock or unittest.mock for stubs/mocks
  • Use assert …, "message" over bare asserts
  • Reflect all API/behaviour changes in docs/ and update roadmap on completion
  • Files must not exceed 400 logical lines:
    • Decompose large modules into subpackages
    • Split large match/case or dispatch tables by domain and collocate with targets if appropriate
    • Move bulky data (fixtures, templates) to external files for parsing at runtime
  • Mutable defaults and shadowed built-ins are forbidden
  • All code must have clear type hints using modern style (A | B, list[str], class Foo[A]:, type Bar = int, etc.), with ABC imports drawn from the correct stdlib module.
  • All path manipulation must be performed using pathlib for cross platform safety. Do not use string manipulation for extraction of path components or filename ele...

Files:

  • tests/workflows/conftest.py
**/*test*.py

📄 CodeRabbit inference engine (.rules/python-00.md)

**/*test*.py: Use pytest idioms. Prefer fixtures to setup/teardown methods. Parametrize broadly. Avoid unnecessary mocks
Group related tests using class with method names prefixed by test_
Write tests from a user's perspective. Test public behaviour, not internals
Avoid mocking too much. Prefer test doubles only for external services or non-deterministic behaviours

In tests, use pytest.raises() with specific exception types and optional regex matchers to constrain expected exceptions, avoiding overly broad exception assertions (B017)

Files:

  • tests/workflows/conftest.py
🧠 Learnings (1)
📚 Learning: 2025-12-27T17:51:29.917Z
Learnt from: leynos
Repo: leynos/shared-actions PR: 219
File: cargo_utils.py:40-41
Timestamp: 2025-12-27T17:51:29.917Z
Learning: In Python files that have 'from __future__ import annotations' (which makes type annotations be evaluated as strings), avoid creating runtime references to types just by annotating attributes. For types used only in annotations or docstrings, import them under a TYPE_CHECKING guard instead of at module import time. Do runtime imports only for actual usage (e.g., Path(...) construction, isinstance checks, classmethod calls, or default values that reference the type). For cargo_utils.py in leynos/shared-actions, ensure runtime behavior is not affected by type-only imports and prefer 'if TYPE_CHECKING' or local imports inside functions where the type is needed at runtime.

Applied to files:

  • tests/workflows/conftest.py
🧬 Code graph analysis (1)
tests/workflows/conftest.py (2)
bool_utils.py (1)
  • coerce_bool (15-57)
tests/workflows/test_action_behaviors.py (1)
  • artifact_dir (25-27)
⏰ Context from checks skipped due to timeout of 120000ms. You can increase the timeout in your CodeRabbit configuration to a maximum of 15 minutes (900000ms). (1)
  • GitHub Check: Sourcery review

Comment thread tests/workflows/conftest.py
Add helper function to consolidate repeated regex assertion pattern
in behavioural tests. Refactor TestDetermineReleaseModes,
TestExportCargoMetadata, and TestStageReleaseArtefacts to use it.

🤖 Generated with [Claude Code](https://claude.com/claude-code)

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
codescene-delta-analysis[bot]

This comment was marked as outdated.

- Update actions/checkout version comment from v4 to v4.3.1 across all
  test workflow files (verified via GitHub API)
- Fix _container_runtime_available() to capture and validate exit code
  instead of discarding the return value from plumbum's run()

🤖 Generated with [Claude Code](https://claude.com/claude-code)

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
codescene-delta-analysis[bot]

This comment was marked as outdated.

- Add syspath-hack dependency to export-cargo-metadata and
  stage-release-artefacts PEP 723 script metadata
- Change bool|str union types to str in CLI function signatures to fix
  cyclopts env var parsing (bool and str consume different token counts)

The coerce_bool utility already handles string-to-bool conversion.

🤖 Generated with [Claude Code](https://claude.com/claude-code)

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
codescene-delta-analysis[bot]

This comment was marked as outdated.

Copy link
Copy Markdown
Contributor

@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: 4

📜 Review details

Configuration used: Organization UI

Review profile: ASSERTIVE

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between a494d16 and e6449e3.

📒 Files selected for processing (9)
  • .github/actions/export-cargo-metadata/scripts/read_manifest.py
  • .github/actions/stage-release-artefacts/scripts/stage.py
  • .github/actions/upload-release-assets/scripts/upload_release_assets.py
  • .github/workflows/test-determine-release-modes.yml
  • .github/workflows/test-export-cargo-metadata.yml
  • .github/workflows/test-stage-release-artefacts.yml
  • .github/workflows/test-upload-release-assets.yml
  • tests/workflows/conftest.py
  • tests/workflows/test_action_behaviors.py
🧰 Additional context used
📓 Path-based instructions (4)
{.github/workflows/**/*.{yml,yaml},.github/actions/**/action.yml}

📄 CodeRabbit inference engine (AGENTS.md)

{.github/workflows/**/*.{yml,yaml},.github/actions/**/action.yml}: Pin third-party actions to a full commit SHA, not just @v1
Request minimum permission set in workflows and avoid GITHUB_TOKEN write unless essential
Job-level permissions: override workflow-level permissions; audit both when setting permissions

Files:

  • .github/workflows/test-export-cargo-metadata.yml
  • .github/workflows/test-upload-release-assets.yml
  • .github/workflows/test-determine-release-modes.yml
  • .github/workflows/test-stage-release-artefacts.yml
{.github/actions/**/src/**/*.{ts,tsx,js,sh},.rules/**/*.py,**/*.py}

📄 CodeRabbit inference engine (AGENTS.md)

After modifying any Python code or GitHub Action logic, ensure make check-fmt, make typecheck, make lint, and make test complete successfully before requesting review

Files:

  • tests/workflows/conftest.py
  • tests/workflows/test_action_behaviors.py
**/*.py

📄 CodeRabbit inference engine (AGENTS.md)

For Python development in actions, follow guidelines in .rules/ directory including Python 3.13 style conventions, context managers, generators, project configuration, exception design, return patterns, and typing

**/*.py: Use snake_case.py for Python file names, named to reflect contents (e.g., http_client.py, task_queue.py)
Use PascalCase for class names
Use snake_case for variable and function names
Use UPPER_SNAKE_CASE for module-level constants
Prefix private or internal helpers with a single underscore (_)
Enable and maintain full static type coverage using Pyright type-checking. Use typing everywhere
Use TypedDict or dataclass for structured data, preferring @dataclass(slots=True) for internal-only usage
Avoid Any type annotation. Prefer precise types (TypeVar, Protocol, Literal, Union) and use typing.cast[...] only when necessary with justification. Use object for unknown-but-opaque values
Be explicit with return type annotations. Use -> None, -> str, etc., for all public functions and class methods
Favour immutability. Prefer tuples to lists, and types.MappingProxyType for read-only mappings
Avoid side effects at import time. Modules should not modify global state or perform actions on import
Use Ruff for formatting. Let Ruff handle whitespace and formatting entirely—do not override
Document public functions, classes, and modules using NumPy docstring format
Use inline comments to explain tricky or non-obvious code logic and decisions

**/*.py: Use context managers to encapsulate setup and teardown logic cleanly and safely, reducing the risk of forgetting to release resources (files, locks, connections, etc.) and simplifying error handling
Use @contextlib.contextmanager decorator for straightforward procedural setup/teardown with linear control flow and no persistent state
Implement __enter__ and __exit__ methods in a class-based context manager when internal state or lifecycle logic spans methods, or when you need...

Files:

  • tests/workflows/conftest.py
  • tests/workflows/test_action_behaviors.py

⚙️ CodeRabbit configuration file

**/*.py: - Keep C90 / mccabe complexity ≤ 9

  • Follow single responsibility and CQRS (command/query segregation)
  • Prefer structural pattern matching to
  • Prefer structural pattern matching over isinstance() or imperative decomposition.
  • Docstrings must follow the numpy style guide. Use a single-line summary for private functions and methods, and full structured docs for all public interfaces.
  • Move conditionals with >2 branches to predicate/helper functions
  • Avoid eval, exec, pickle, monkey-patching, ctypes, unsafe shell
  • Every module must begin with a triple-quoted docstring explaining its purpose, utility, and usage, including example calls if appropriate.
  • Comments and docs must follow en-GB-oxendict (-ize / -yse / -our) spelling and grammar
  • Lint suppressions:
    • Blanket # noqa, file-level skips, and categories are forbidden
    • Only narrow in-line disables (# noqa: XYZ) are permitted, and must be accompanied by FIXME: or a ticket link, and used only as a last resort.
  • Use pytest fixtures for shared setup (conftest.py or fixtures/)
  • Replace duplicate tests with @pytest.mark.parametrize
  • Prefer pytest-mock or unittest.mock for stubs/mocks
  • Use assert …, "message" over bare asserts
  • Reflect all API/behaviour changes in docs/ and update roadmap on completion
  • Files must not exceed 400 logical lines:
    • Decompose large modules into subpackages
    • Split large match/case or dispatch tables by domain and collocate with targets if appropriate
    • Move bulky data (fixtures, templates) to external files for parsing at runtime
  • Mutable defaults and shadowed built-ins are forbidden
  • All code must have clear type hints using modern style (A | B, list[str], class Foo[A]:, type Bar = int, etc.), with ABC imports drawn from the correct stdlib module.
  • All path manipulation must be performed using pathlib for cross platform safety. Do not use string manipulation for extraction of path components or filename ele...

Files:

  • tests/workflows/conftest.py
  • tests/workflows/test_action_behaviors.py
**/*test*.py

📄 CodeRabbit inference engine (.rules/python-00.md)

**/*test*.py: Use pytest idioms. Prefer fixtures to setup/teardown methods. Parametrize broadly. Avoid unnecessary mocks
Group related tests using class with method names prefixed by test_
Write tests from a user's perspective. Test public behaviour, not internals
Avoid mocking too much. Prefer test doubles only for external services or non-deterministic behaviours

In tests, use pytest.raises() with specific exception types and optional regex matchers to constrain expected exceptions, avoiding overly broad exception assertions (B017)

Files:

  • tests/workflows/conftest.py
  • tests/workflows/test_action_behaviors.py
🧠 Learnings (1)
📚 Learning: 2025-12-27T17:51:29.917Z
Learnt from: leynos
Repo: leynos/shared-actions PR: 219
File: cargo_utils.py:40-41
Timestamp: 2025-12-27T17:51:29.917Z
Learning: In Python files that have 'from __future__ import annotations' (which makes type annotations be evaluated as strings), avoid creating runtime references to types just by annotating attributes. For types used only in annotations or docstrings, import them under a TYPE_CHECKING guard instead of at module import time. Do runtime imports only for actual usage (e.g., Path(...) construction, isinstance checks, classmethod calls, or default values that reference the type). For cargo_utils.py in leynos/shared-actions, ensure runtime behavior is not affected by type-only imports and prefer 'if TYPE_CHECKING' or local imports inside functions where the type is needed at runtime.

Applied to files:

  • tests/workflows/conftest.py
  • tests/workflows/test_action_behaviors.py
🧬 Code graph analysis (5)
tests/workflows/conftest.py (2)
bool_utils.py (1)
  • coerce_bool (15-57)
tests/workflows/test_action_behaviors.py (1)
  • artifact_dir (25-27)
.github/actions/export-cargo-metadata/scripts/read_manifest.py (1)
cargo_utils.py (5)
  • ManifestError (44-68)
  • get_bin_name (168-212)
  • get_package_field (107-152)
  • read_manifest (71-104)
  • resolve_version (327-373)
tests/workflows/test_action_behaviors.py (1)
tests/workflows/conftest.py (2)
  • ActConfig (56-62)
  • run_act (65-123)
.github/actions/stage-release-artefacts/scripts/stage.py (5)
.github/actions/stage-release-artefacts/scripts/stage_common/errors.py (1)
  • StageError (6-7)
.github/actions/stage-release-artefacts/scripts/stage_common/config.py (2)
  • load_config (83-138)
  • staging_dir (53-55)
.github/actions/stage-release-artefacts/scripts/stage_common/environment.py (1)
  • require_env_path (13-35)
.github/actions/stage-release-artefacts/scripts/stage_common/pipeline.py (1)
  • stage_artefacts (85-150)
bool_utils.py (1)
  • coerce_bool (15-57)
.github/actions/upload-release-assets/scripts/upload_release_assets.py (1)
bool_utils.py (1)
  • coerce_bool (15-57)
🔍 Remote MCP

I'll search for relevant information to help contextualize this pull request about migrating release workflow actions.Based on my research, I can now provide contextual information to help with reviewing this pull request.

Summary of Additional Context for PR Review

Technologies & Patterns Used

Cyclopts CLI Framework:
Cyclopts is an intuitive CLI framework with advanced type hinting support, rich help generation, and customizable converters and validators. The PR adopts this framework consistently across multiple action scripts for standardized CLI parameter parsing and environment variable handling.

GitHub Composite Actions Best Practices:
Composite actions allow you to collect a series of workflow job steps into a single action which you can then run as a single job step in multiple workflows. Composite actions combine multiple steps that you can then run within a job step, whereas reusable workflows allow you to reuse an entire workflow with multiple jobs and steps. This PR appropriately uses composite actions for step-level encapsulation, stored in the recommended .github/actions subfolder pattern.

Code Organization & Dependency Structure

The PR follows established patterns:

  • Local action placement: All four new actions (determine-release-modes, export-cargo-metadata, stage-release-artefacts, upload-release-assets) follow the standard .github/actions/<action-name>/ directory structure with action.yml metadata files.
  • Shared utilities: Centralized cargo_utils.py and bool_utils.py at the repository root promote code reuse, addressing the duplication issues identified in PR comments about boolean coercion logic.
  • Test harness patterns: The new tests/workflows/ test infrastructure with conftest.py, fixture files, and behavioral tests using act demonstrate effort to validate composite actions end-to-end.

Review Considerations Based on PR Comments

The PR feedback history indicates several focused improvements:

  1. Complexity reduction: Validation logic in TOML parsing was refactored to reduce nested conditions while preserving error messages.
  2. Boolean coercion centralization: Previously duplicated logic is now unified in bool_utils.py with coerce_bool() and coerce_bool_strict() for consistent input handling across scripts.
  3. Output writing: Directory creation before writing to GITHUB_OUTPUT was added to handle missing parent directories.
  4. Path collision avoidance: Checksum maps updated to use relative paths rather than basenames to prevent overwrites.
  5. Test coverage: 147 new unit tests added, though some edge cases (e.g., parent directory creation regression tests) were iteratively identified and addressed.

Key Files & Patterns to Verify

  • action.yml files: Each composite action defines inputs, outputs, and a two-step pattern: setup uv via astral-sh/setup-uv@, then run a Python script with environment variable bindings.
  • PEP 723 compliance: Scripts include script metadata (via docstrings or headers) for dependency declaration.
  • Environment variable passing: Inputs are passed as uppercase INPUT_* environment variables from action.yml to Python scripts, consistent with GitHub Actions conventions.
  • Error handling & output: Scripts use GitHub Actions output format (GITHUB_OUTPUT file writing) and stderr for diagnostic messages.

[::web_search::], [::Context7::], [::Deepwiki::]

⏰ Context from checks skipped due to timeout of 120000ms. You can increase the timeout in your CodeRabbit configuration to a maximum of 15 minutes (900000ms). (6)
  • GitHub Check: python-tests (macos-15)
  • GitHub Check: python-tests (ubuntu-latest)
  • GitHub Check: python-tests-windows
  • GitHub Check: build-release (windows-latest, aarch64-pc-windows-gnullvm)
  • GitHub Check: build-release (ubuntu-latest, x86_64-unknown-linux-gnu)
  • GitHub Check: build-release (windows-latest, x86_64-pc-windows-gnullvm)
🔇 Additional comments (30)
.github/workflows/test-export-cargo-metadata.yml (3)

6-10: LGTM!

Job-level permissions correctly declare contents: read, following least-privilege principles. This addresses previous review feedback.


14-29: LGTM!

Action invocation and output verification logic are correct. The semver regex appropriately validates the core version whilst permitting pre-release suffixes.


12-12: No action required. The SHA 34e114876b0b11c390a56381ad16ebd13914f8d5 correctly corresponds to v4.3.1 and the comment is accurate. The commit message references "v6 auth style" as the cleanup topic, not the release version.

.github/actions/export-cargo-metadata/scripts/read_manifest.py (4)

1-51: LGTM on module setup and imports.

The PEP 723 header correctly declares dependencies, and the import structure properly uses syspath_hack to enable shared utility imports. The cyclopts configuration for environment variable parsing follows the established pattern.


54-73: LGTM on output/env helpers.

Both _write_output and _write_env correctly create parent directories before writing, addressing resilience concerns from previous reviews.


102-122: LGTM on field extraction with structural pattern matching.

The match/case dispatch is clean and handles the description case safely by checking isinstance(package, dict) before accessing it.


182-209: LGTM on main entry point.

The main function correctly validates the boolean input using coerce_bool_strict, loads the manifest with error handling, and processes fields with appropriate output. The flow is clear and error states are properly communicated via GitHub Actions annotations.

.github/actions/upload-release-assets/scripts/upload_release_assets.py (7)

1-53: LGTM on module setup.

The PEP 723 dependencies correctly include syspath-hack, and the import structure is sound. The TYPE_CHECKING guard for BoundCommand is appropriate.


55-66: LGTM on data structures.

AssetError and ReleaseAsset are well-defined. The frozen dataclass ensures immutability for release assets.


117-160: LGTM on asset discovery.

The discover_assets function properly validates directory existence, detects empty artefacts, checks for name collisions, and raises AssetError with clear messages. The docstring follows NumPy format.


173-228: LGTM on upload logic.

The upload_assets function correctly handles dry-run mode with accurate clobber flag representation, and lazily initialises the gh command only when needed.


231-239: LGTM on output helper.

The _write_output function correctly creates parent directories before writing, matching the pattern in other actions.


242-302: LGTM on main entry point.

The error handling covers both AssetError from discovery and ProcessExecutionError/CommandNotFound from upload. Output variables are consistently written for both success and failure cases.


305-327: LGTM on CLI wrapper.

The cli function correctly uses coerce_bool with appropriate defaults for dry_run and clobber parameters.

.github/workflows/test-upload-release-assets.yml (1)

1-36: LGTM on workflow definition.

The workflow correctly:

  • Requests minimal contents: read permissions
  • Pins actions/checkout to a full commit SHA
  • Uses kebab-case output references matching the action definition
  • Validates dry-run behaviour with appropriate assertions
.github/workflows/test-stage-release-artefacts.yml (1)

1-60: LGTM on workflow definition.

The workflow correctly:

  • Requests minimal contents: read permissions
  • Pins actions/checkout to a full commit SHA
  • Uses kebab-case output references consistently
  • Creates a valid TOML staging configuration
  • Verifies the staging directory, binary, and checksum sidecar exist
.github/actions/stage-release-artefacts/scripts/stage.py (2)

1-48: LGTM on module setup.

The PEP 723 dependencies are correct, and the dual use of prepend_to_syspath (for local stage_common) and prepend_project_root (for shared bool_utils) is appropriate given the module structure.


51-93: LGTM on main entry point.

The main function correctly:

  • Loads configuration with proper path handling
  • Coerces boolean input with coerce_bool
  • Handles errors with GitHub Actions-formatted messages
  • Reports success with a relative path summary
tests/workflows/conftest.py (4)

1-16: LGTM on module setup.

The imports are appropriate, FIXTURES_DIR correctly uses pathlib.Path, and the coerce_bool import enables consistent boolean handling.


18-42: LGTM on availability checks.

The helper functions correctly:

  • Use shutil.which for executable detection
  • Capture and validate the exit code from container runtime checks
  • Use coerce_bool for environment variable parsing

44-52: LGTM on skip markers.

The pytest skip markers are well-structured with clear reasons.


55-123: LGTM on ActConfig and run_act.

The ActConfig dataclass uses slots=True per guidelines, and run_act properly:

  • Creates the artefact directory
  • Falls back to fixture event paths
  • Handles timeouts gracefully
  • Returns combined stdout/stderr for log assertions
tests/workflows/test_action_behaviors.py (7)

1-22: LGTM on module setup.

The module docstring uses en-GB spelling, and the TYPE_CHECKING guard for Path correctly avoids runtime import costs per the learnings.


24-55: LGTM on fixtures and helpers.

The artifact_dir fixture is appropriately scoped, and _run_act_and_get_logs provides a clean abstraction for test setup with built-in success assertion.


57-73: LGTM on assertion helper.

The _assert_log_patterns function consolidates regex assertions with clear error messages, reducing duplication across tests.


75-127: LGTM on TestDetermineReleaseModes.

The parametrised test_dry_run_mode consolidates duplicate tests, and the regex patterns are sufficiently specific to avoid false positives.


130-152: LGTM on TestExportCargoMetadata.

The test verifies non-empty values for name and version using appropriate regex patterns.


154-181: LGTM on TestStageReleaseArtefacts.

The test verifies staging outputs with appropriate patterns for artifact-dir and staged-files.


184-205: LGTM on TestUploadReleaseAssets.

The test correctly extracts and validates uploaded-count as a numeric value and checks upload-error=false.

.github/workflows/test-determine-release-modes.yml (1)

19-20: LGTM!

Minimal permissions (contents: read) appropriately scoped for a test workflow that checks out code and validates action outputs.

Comment thread .github/actions/export-cargo-metadata/scripts/read_manifest.py
Comment thread .github/workflows/test-determine-release-modes.yml
Comment thread .github/workflows/test-determine-release-modes.yml
@leynos
Copy link
Copy Markdown
Owner Author

leynos commented Dec 29, 2025

@coderabbitai Please suggest a fix for this issue and supply a prompt for an AI coding agent to enable it to apply the fix:

Code Duplication

tests/workflows/test_action_behaviors.py:

What lead to degradation?

The module contains 2 functions with similar structure: TestDetermineReleaseModes.test_dry_run_mode,TestDetermineReleaseModes.test_push_tag_event

Why does this problem occur?

Duplicated code often leads to code that's harder to change since the same logical change has to be done in multiple functions. More duplication gives lower code health.

How to fix it?

A certain degree of duplicated code might be acceptable. The problems start when it is the same behavior that is duplicated across the functions in the module, ie. a violation of the Don't Repeat Yourself (DRY) principle. DRY violations lead to code that is changed together in predictable patterns, which is both expensive and risky. DRY violations can be identified using CodeScene's X-Ray analysis to detect clusters of change coupled functions with high code similarity. Read More
Once you have identified the similarities across functions, look to extract and encapsulate the concept that varies into its own function(s). These shared abstractions can then be re-used, which minimizes the amount of duplication and simplifies change.

@coderabbitai

This comment was marked as resolved.

- Consolidate TestDetermineReleaseModes tests into parametrized test
- Add comment explaining field name kebab-case normalisation
- Use match/case in _is_candidate for artefact detection
- Wire workflow_call inputs through to determine-release-modes action

🤖 Generated with [Claude Code](https://claude.com/claude-code)

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
Copy link
Copy Markdown
Contributor

@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

♻️ Duplicate comments (1)
.github/workflows/test-determine-release-modes.yml (1)

31-39: Strengthen test coverage with event-specific value assertions.

The verification only checks outputs are boolean strings, but does not assert expected values for specific event types. Add conditional assertions to verify:

  • workflow_call events produce dry-run=true
  • pull_request events produce dry-run=true and should-publish=false
  • push events on v* tags produce dry-run=false and should-publish=true

This will catch regressions in the action's event-normalisation logic rather than only validating output format.

🔎 Example enhanced verification
       - name: Verify outputs
         run: |
           echo "dry-run=${{ steps.modes.outputs.dry-run }}"
           echo "should-publish=${{ steps.modes.outputs.should-publish }}"
           echo "should-upload-workflow-artifacts=${{ steps.modes.outputs.should-upload-workflow-artifacts }}"
           # Verify outputs are boolean strings
           [[ "${{ steps.modes.outputs.dry-run }}" =~ ^(true|false)$ ]]
           [[ "${{ steps.modes.outputs.should-publish }}" =~ ^(true|false)$ ]]
           [[ "${{ steps.modes.outputs.should-upload-workflow-artifacts }}" =~ ^(true|false)$ ]]
+
+      - name: Verify logic for pull_request events
+        if: github.event_name == 'pull_request'
+        run: |
+          # Expect dry-run for PRs
+          [[ "${{ steps.modes.outputs.dry-run }}" == "true" ]]
+          [[ "${{ steps.modes.outputs.should-publish }}" == "false" ]]
+
+      - name: Verify logic for tag push events
+        if: github.event_name == 'push' && startsWith(github.ref, 'refs/tags/v')
+        run: |
+          # Expect publish for version tags
+          [[ "${{ steps.modes.outputs.dry-run }}" == "false" ]]
+          [[ "${{ steps.modes.outputs.should-publish }}" == "true" ]]
+
+      - name: Verify logic for workflow_call events
+        if: github.event_name == 'workflow_call'
+        run: |
+          # Workflow call respects inputs (defaults: dry-run=true, publish=false)
+          [[ "${{ steps.modes.outputs.dry-run }}" == "${{ inputs.dry-run }}" ]]
+          [[ "${{ steps.modes.outputs.should-publish }}" == "${{ inputs.publish }}" ]]
🤖 Prompt for AI Agents
.github/workflows/test-determine-release-modes.yml around lines 31-39: add event-specific value assertions to verify correctness of outputs per trigger scenario—after the existing boolean format checks, add three new conditional steps: (1) a step with if: github.event_name == 'pull_request' that asserts dry-run == "true" and should-publish == "false"; (2) a step with if: github.event_name == 'push' && startsWith(github.ref, 'refs/tags/v') that asserts dry-run == "false" and should-publish == "true"; (3) a step with if: github.event_name == 'workflow_call' that asserts dry-run == inputs.dry-run and should-publish == inputs.publish; use bash [[ ]] assertions with descriptive comments in each step so the workflow catches regressions in event-normalisation logic.
📜 Review details

Configuration used: Organization UI

Review profile: ASSERTIVE

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between e6449e3 and 85d7224.

📒 Files selected for processing (4)
  • .github/actions/export-cargo-metadata/scripts/read_manifest.py
  • .github/actions/upload-release-assets/scripts/upload_release_assets.py
  • .github/workflows/test-determine-release-modes.yml
  • tests/workflows/test_action_behaviors.py
🧰 Additional context used
📓 Path-based instructions (4)
{.github/workflows/**/*.{yml,yaml},.github/actions/**/action.yml}

📄 CodeRabbit inference engine (AGENTS.md)

{.github/workflows/**/*.{yml,yaml},.github/actions/**/action.yml}: Pin third-party actions to a full commit SHA, not just @v1
Request minimum permission set in workflows and avoid GITHUB_TOKEN write unless essential
Job-level permissions: override workflow-level permissions; audit both when setting permissions

Files:

  • .github/workflows/test-determine-release-modes.yml
{.github/actions/**/src/**/*.{ts,tsx,js,sh},.rules/**/*.py,**/*.py}

📄 CodeRabbit inference engine (AGENTS.md)

After modifying any Python code or GitHub Action logic, ensure make check-fmt, make typecheck, make lint, and make test complete successfully before requesting review

Files:

  • tests/workflows/test_action_behaviors.py
**/*.py

📄 CodeRabbit inference engine (AGENTS.md)

For Python development in actions, follow guidelines in .rules/ directory including Python 3.13 style conventions, context managers, generators, project configuration, exception design, return patterns, and typing

**/*.py: Use snake_case.py for Python file names, named to reflect contents (e.g., http_client.py, task_queue.py)
Use PascalCase for class names
Use snake_case for variable and function names
Use UPPER_SNAKE_CASE for module-level constants
Prefix private or internal helpers with a single underscore (_)
Enable and maintain full static type coverage using Pyright type-checking. Use typing everywhere
Use TypedDict or dataclass for structured data, preferring @dataclass(slots=True) for internal-only usage
Avoid Any type annotation. Prefer precise types (TypeVar, Protocol, Literal, Union) and use typing.cast[...] only when necessary with justification. Use object for unknown-but-opaque values
Be explicit with return type annotations. Use -> None, -> str, etc., for all public functions and class methods
Favour immutability. Prefer tuples to lists, and types.MappingProxyType for read-only mappings
Avoid side effects at import time. Modules should not modify global state or perform actions on import
Use Ruff for formatting. Let Ruff handle whitespace and formatting entirely—do not override
Document public functions, classes, and modules using NumPy docstring format
Use inline comments to explain tricky or non-obvious code logic and decisions

**/*.py: Use context managers to encapsulate setup and teardown logic cleanly and safely, reducing the risk of forgetting to release resources (files, locks, connections, etc.) and simplifying error handling
Use @contextlib.contextmanager decorator for straightforward procedural setup/teardown with linear control flow and no persistent state
Implement __enter__ and __exit__ methods in a class-based context manager when internal state or lifecycle logic spans methods, or when you need...

Files:

  • tests/workflows/test_action_behaviors.py

⚙️ CodeRabbit configuration file

**/*.py: - Keep C90 / mccabe complexity ≤ 9

  • Follow single responsibility and CQRS (command/query segregation)
  • Prefer structural pattern matching to
  • Prefer structural pattern matching over isinstance() or imperative decomposition.
  • Docstrings must follow the numpy style guide. Use a single-line summary for private functions and methods, and full structured docs for all public interfaces.
  • Move conditionals with >2 branches to predicate/helper functions
  • Avoid eval, exec, pickle, monkey-patching, ctypes, unsafe shell
  • Every module must begin with a triple-quoted docstring explaining its purpose, utility, and usage, including example calls if appropriate.
  • Comments and docs must follow en-GB-oxendict (-ize / -yse / -our) spelling and grammar
  • Lint suppressions:
    • Blanket # noqa, file-level skips, and categories are forbidden
    • Only narrow in-line disables (# noqa: XYZ) are permitted, and must be accompanied by FIXME: or a ticket link, and used only as a last resort.
  • Use pytest fixtures for shared setup (conftest.py or fixtures/)
  • Replace duplicate tests with @pytest.mark.parametrize
  • Prefer pytest-mock or unittest.mock for stubs/mocks
  • Use assert …, "message" over bare asserts
  • Reflect all API/behaviour changes in docs/ and update roadmap on completion
  • Files must not exceed 400 logical lines:
    • Decompose large modules into subpackages
    • Split large match/case or dispatch tables by domain and collocate with targets if appropriate
    • Move bulky data (fixtures, templates) to external files for parsing at runtime
  • Mutable defaults and shadowed built-ins are forbidden
  • All code must have clear type hints using modern style (A | B, list[str], class Foo[A]:, type Bar = int, etc.), with ABC imports drawn from the correct stdlib module.
  • All path manipulation must be performed using pathlib for cross platform safety. Do not use string manipulation for extraction of path components or filename ele...

Files:

  • tests/workflows/test_action_behaviors.py
**/*test*.py

📄 CodeRabbit inference engine (.rules/python-00.md)

**/*test*.py: Use pytest idioms. Prefer fixtures to setup/teardown methods. Parametrize broadly. Avoid unnecessary mocks
Group related tests using class with method names prefixed by test_
Write tests from a user's perspective. Test public behaviour, not internals
Avoid mocking too much. Prefer test doubles only for external services or non-deterministic behaviours

In tests, use pytest.raises() with specific exception types and optional regex matchers to constrain expected exceptions, avoiding overly broad exception assertions (B017)

Files:

  • tests/workflows/test_action_behaviors.py
🧠 Learnings (1)
📚 Learning: 2025-12-27T17:51:29.917Z
Learnt from: leynos
Repo: leynos/shared-actions PR: 219
File: cargo_utils.py:40-41
Timestamp: 2025-12-27T17:51:29.917Z
Learning: In Python files that have 'from __future__ import annotations' (which makes type annotations be evaluated as strings), avoid creating runtime references to types just by annotating attributes. For types used only in annotations or docstrings, import them under a TYPE_CHECKING guard instead of at module import time. Do runtime imports only for actual usage (e.g., Path(...) construction, isinstance checks, classmethod calls, or default values that reference the type). For cargo_utils.py in leynos/shared-actions, ensure runtime behavior is not affected by type-only imports and prefer 'if TYPE_CHECKING' or local imports inside functions where the type is needed at runtime.

Applied to files:

  • tests/workflows/test_action_behaviors.py
🧬 Code graph analysis (3)
.github/actions/export-cargo-metadata/scripts/read_manifest.py (2)
bool_utils.py (1)
  • coerce_bool_strict (60-100)
cargo_utils.py (5)
  • ManifestError (44-68)
  • get_bin_name (168-212)
  • get_package_field (107-152)
  • read_manifest (71-104)
  • resolve_version (327-373)
tests/workflows/test_action_behaviors.py (1)
tests/workflows/conftest.py (2)
  • ActConfig (56-62)
  • run_act (65-123)
.github/actions/upload-release-assets/scripts/upload_release_assets.py (1)
bool_utils.py (1)
  • coerce_bool (15-57)
⏰ Context from checks skipped due to timeout of 120000ms. You can increase the timeout in your CodeRabbit configuration to a maximum of 15 minutes (900000ms). (6)
  • GitHub Check: python-tests (macos-15)
  • GitHub Check: python-tests-windows
  • GitHub Check: python-tests (ubuntu-latest)
  • GitHub Check: build-release (windows-latest, x86_64-pc-windows-gnullvm)
  • GitHub Check: build-release (macos-15, x86_64-apple-darwin)
  • GitHub Check: build-release (windows-latest, aarch64-pc-windows-gnullvm)
🔇 Additional comments (16)
.github/actions/export-cargo-metadata/scripts/read_manifest.py (3)

54-73: Excellent resilience improvement.

Both output-writing helpers now create parent directories before opening files, making them consistent with other actions in the repository and resilient to missing directory structure.


120-120: Verify test coverage for description trimming.

The implementation correctly strips whitespace from description values (line 120). Confirm that a unit test validates this behaviour—the past review comments indicate a description-trimming test was outstanding.

#!/bin/bash
# Description: Check for tests covering description whitespace trimming.

# Search for tests that verify description field extraction and whitespace handling
rg -n -C5 --type=py 'description.*strip|strip.*description|test.*description.*whitespace' .github/actions/export-cargo-metadata/tests/

54-73: Verify test coverage for parent directory creation.

Both _write_output and _write_env now create parent directories (lines 60, 71). Confirm that a regression test validates this behaviour—the past review comments indicate a GITHUB_OUTPUT parent-directory test was outstanding.

#!/bin/bash
# Description: Check for tests covering parent directory creation for output/env files.

# Search for tests that verify mkdir behaviour or missing parent directories
rg -n -C5 --type=py 'mkdir|parent.*exist|GITHUB_OUTPUT.*parent|GITHUB_ENV.*parent|test.*output.*directory' .github/actions/export-cargo-metadata/tests/
tests/workflows/test_action_behaviors.py (7)

1-21: LGTM: proper module structure and TYPE_CHECKING usage.

The module docstring uses correct en-GB spelling throughout, and the TYPE_CHECKING guard properly isolates the Path import for type-checking only, consistent with the project's typing conventions.


24-27: LGTM: clean fixture implementation.

The fixture follows pytest conventions and uses proper type hints.


30-54: LGTM: well-structured helper function.

The helper properly encapsulates act execution with success assertion, includes comprehensive documentation, and provides clear error messages on failure.


57-72: LGTM: clean assertion helper.

The helper provides good abstraction for regex-based log assertions with proper type hints and keyword-only flags parameter.


75-115: LGTM: effective parametrised test.

The test consolidates three scenarios using @pytest.mark.parametrize, employs the extracted helper functions, and uses flexible regex patterns that handle output format variations. The parametrisation eliminates duplication while maintaining clear test intent.


118-139: LGTM: strong metadata validation.

The test uses regex patterns that verify both presence and non-empty values for exported metadata, with clear error messages distinguishing between missing and empty values.


142-169: LGTM: consistent staging validation.

The test follows the same strong assertion pattern as the cargo metadata test, verifying both presence and non-empty values for staging outputs.

.github/actions/upload-release-assets/scripts/upload_release_assets.py (6)

1-53: Excellent setup—all past issues resolved.

The PEP 723 dependencies now correctly include syspath-hack, cyclopts version constraint has been updated to allow 3.x releases, and the script imports coerce_bool from the shared bool_utils module rather than duplicating the logic. The module docstring is clear and provides helpful usage examples.


55-68: Clean data model and configuration.

The AssetError exception provides clear error signalling, the frozen ReleaseAsset dataclass is well-designed, and the App configuration correctly uses the INPUT_ prefix for GitHub Actions environment variables.


83-96: Path resolution and iteration logic is sound.

The _resolve_asset_name function correctly generates unique names for nested artefacts by replacing path separators with double underscores, and _iter_candidate_paths ensures deterministic ordering by sorting results. Both helpers are well-designed.


99-162: Robust discovery and validation logic.

The validation helpers (_require_non_empty, _register_asset) provide excellent defensive checks, and discover_assets orchestrates them correctly. Error messages are informative, the docstring is comprehensive, and the function handles all edge cases appropriately (missing directory, empty files, name collisions, no artefacts found).


165-241: Upload logic correctly addresses all past review feedback.

The dry-run output now conditionally includes --clobber based on the parameter value (line 214), and _write_output defensively creates parent directories before writing (line 239). The upload_assets function handles both dry-run and live modes cleanly, with appropriate error handling for ProcessExecutionError and CommandNotFound.


244-332: Well-structured entry points with comprehensive error handling.

The main function correctly handles all error cases (discovery failures, upload failures) and writes appropriate outputs for downstream steps. The cli wrapper uses coerce_bool from the shared bool_utils module, eliminating the code duplication flagged in earlier reviews. All error paths are properly surfaced via GITHUB_OUTPUT.

Comment thread .github/actions/upload-release-assets/scripts/upload_release_assets.py Outdated
Add conditional verification steps that assert expected output values
for each trigger type:
- pull_request: dry-run=true, should-publish=false
- push tag: dry-run=false, should-publish=true
- workflow_call: outputs match provided inputs

This strengthens test coverage to catch regressions in the action's
event-normalisation logic.

🤖 Generated with [Claude Code](https://claude.com/claude-code)

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
@leynos
Copy link
Copy Markdown
Owner Author

leynos commented Dec 29, 2025

@coderabbitai review

@coderabbitai
Copy link
Copy Markdown
Contributor

coderabbitai Bot commented Dec 29, 2025

✅ Actions performed

Review triggered.

Note: CodeRabbit is an incremental review system and does not re-review already reviewed commits. This command is applicable only when automatic reviews are paused.

- Revert match/case to simpler if/else chain in _is_candidate as the
  pattern match offered no structural benefits
- Remove redundant uploaded_match.group(1) assertion since the regex
  (\d+) guarantees a non-empty digit group

🤖 Generated with [Claude Code](https://claude.com/claude-code)

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
Copy link
Copy Markdown
Contributor

@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

📜 Review details

Configuration used: Organization UI

Review profile: ASSERTIVE

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between 513d3fb and c5fa1f1.

📒 Files selected for processing (2)
  • .github/actions/upload-release-assets/scripts/upload_release_assets.py
  • tests/workflows/test_action_behaviors.py
🧰 Additional context used
📓 Path-based instructions (3)
{.github/actions/**/src/**/*.{ts,tsx,js,sh},.rules/**/*.py,**/*.py}

📄 CodeRabbit inference engine (AGENTS.md)

After modifying any Python code or GitHub Action logic, ensure make check-fmt, make typecheck, make lint, and make test complete successfully before requesting review

Files:

  • tests/workflows/test_action_behaviors.py
**/*.py

📄 CodeRabbit inference engine (AGENTS.md)

For Python development in actions, follow guidelines in .rules/ directory including Python 3.13 style conventions, context managers, generators, project configuration, exception design, return patterns, and typing

**/*.py: Use snake_case.py for Python file names, named to reflect contents (e.g., http_client.py, task_queue.py)
Use PascalCase for class names
Use snake_case for variable and function names
Use UPPER_SNAKE_CASE for module-level constants
Prefix private or internal helpers with a single underscore (_)
Enable and maintain full static type coverage using Pyright type-checking. Use typing everywhere
Use TypedDict or dataclass for structured data, preferring @dataclass(slots=True) for internal-only usage
Avoid Any type annotation. Prefer precise types (TypeVar, Protocol, Literal, Union) and use typing.cast[...] only when necessary with justification. Use object for unknown-but-opaque values
Be explicit with return type annotations. Use -> None, -> str, etc., for all public functions and class methods
Favour immutability. Prefer tuples to lists, and types.MappingProxyType for read-only mappings
Avoid side effects at import time. Modules should not modify global state or perform actions on import
Use Ruff for formatting. Let Ruff handle whitespace and formatting entirely—do not override
Document public functions, classes, and modules using NumPy docstring format
Use inline comments to explain tricky or non-obvious code logic and decisions

**/*.py: Use context managers to encapsulate setup and teardown logic cleanly and safely, reducing the risk of forgetting to release resources (files, locks, connections, etc.) and simplifying error handling
Use @contextlib.contextmanager decorator for straightforward procedural setup/teardown with linear control flow and no persistent state
Implement __enter__ and __exit__ methods in a class-based context manager when internal state or lifecycle logic spans methods, or when you need...

Files:

  • tests/workflows/test_action_behaviors.py

⚙️ CodeRabbit configuration file

**/*.py: - Keep C90 / mccabe complexity ≤ 9

  • Follow single responsibility and CQRS (command/query segregation)
  • Prefer structural pattern matching to
  • Prefer structural pattern matching over isinstance() or imperative decomposition.
  • Docstrings must follow the numpy style guide. Use a single-line summary for private functions and methods, and full structured docs for all public interfaces.
  • Move conditionals with >2 branches to predicate/helper functions
  • Avoid eval, exec, pickle, monkey-patching, ctypes, unsafe shell
  • Every module must begin with a triple-quoted docstring explaining its purpose, utility, and usage, including example calls if appropriate.
  • Comments and docs must follow en-GB-oxendict (-ize / -yse / -our) spelling and grammar
  • Lint suppressions:
    • Blanket # noqa, file-level skips, and categories are forbidden
    • Only narrow in-line disables (# noqa: XYZ) are permitted, and must be accompanied by FIXME: or a ticket link, and used only as a last resort.
  • Use pytest fixtures for shared setup (conftest.py or fixtures/)
  • Replace duplicate tests with @pytest.mark.parametrize
  • Prefer pytest-mock or unittest.mock for stubs/mocks
  • Use assert …, "message" over bare asserts
  • Reflect all API/behaviour changes in docs/ and update roadmap on completion
  • Files must not exceed 400 logical lines:
    • Decompose large modules into subpackages
    • Split large match/case or dispatch tables by domain and collocate with targets if appropriate
    • Move bulky data (fixtures, templates) to external files for parsing at runtime
  • Mutable defaults and shadowed built-ins are forbidden
  • All code must have clear type hints using modern style (A | B, list[str], class Foo[A]:, type Bar = int, etc.), with ABC imports drawn from the correct stdlib module.
  • All path manipulation must be performed using pathlib for cross platform safety. Do not use string manipulation for extraction of path components or filename ele...

Files:

  • tests/workflows/test_action_behaviors.py
**/*test*.py

📄 CodeRabbit inference engine (.rules/python-00.md)

**/*test*.py: Use pytest idioms. Prefer fixtures to setup/teardown methods. Parametrize broadly. Avoid unnecessary mocks
Group related tests using class with method names prefixed by test_
Write tests from a user's perspective. Test public behaviour, not internals
Avoid mocking too much. Prefer test doubles only for external services or non-deterministic behaviours

In tests, use pytest.raises() with specific exception types and optional regex matchers to constrain expected exceptions, avoiding overly broad exception assertions (B017)

Files:

  • tests/workflows/test_action_behaviors.py
🧠 Learnings (1)
📚 Learning: 2025-12-27T17:51:29.917Z
Learnt from: leynos
Repo: leynos/shared-actions PR: 219
File: cargo_utils.py:40-41
Timestamp: 2025-12-27T17:51:29.917Z
Learning: In Python files that have 'from __future__ import annotations' (which makes type annotations be evaluated as strings), avoid creating runtime references to types just by annotating attributes. For types used only in annotations or docstrings, import them under a TYPE_CHECKING guard instead of at module import time. Do runtime imports only for actual usage (e.g., Path(...) construction, isinstance checks, classmethod calls, or default values that reference the type). For cargo_utils.py in leynos/shared-actions, ensure runtime behavior is not affected by type-only imports and prefer 'if TYPE_CHECKING' or local imports inside functions where the type is needed at runtime.

Applied to files:

  • tests/workflows/test_action_behaviors.py
🧬 Code graph analysis (1)
tests/workflows/test_action_behaviors.py (1)
tests/workflows/conftest.py (2)
  • ActConfig (56-62)
  • run_act (65-123)
⏰ Context from checks skipped due to timeout of 120000ms. You can increase the timeout in your CodeRabbit configuration to a maximum of 15 minutes (900000ms). (5)
  • GitHub Check: python-tests-windows
  • GitHub Check: python-tests (ubuntu-latest)
  • GitHub Check: python-tests (macos-15)
  • GitHub Check: build-release (windows-latest, aarch64-pc-windows-gnullvm)
  • GitHub Check: build-release (windows-latest, x86_64-pc-windows-gnullvm)
🔇 Additional comments (9)
tests/workflows/test_action_behaviors.py (8)

1-9: Well-documented module with correct en-GB spelling.

The docstring clearly explains the test suite's purpose, prerequisites, and execution instructions.


11-21: Import structure follows best practices.

Proper use of TYPE_CHECKING guard for Path since it's only used in type annotations. Based on learnings, this avoids unnecessary runtime imports.


24-27: Simple and effective fixture.


30-54: Clean helper with informative failure messages.

The assertion includes the full logs on failure, which aids debugging. NumPy docstring format is correctly applied.


57-72: Useful helper reducing assertion duplication.

The flags: int type is acceptable since re.RegexFlag is an IntFlag. For stricter typing, use re.RegexFlag instead, but this is optional.


75-115: Well-structured parametrized test with flexible patterns.

Good consolidation of the three event scenarios into a single parametrized test. The regex patterns correctly handle format variations (hyphens/underscores, different delimiters).


118-139: Appropriate assertions verifying non-empty metadata values.

Using \S+ to ensure values are non-empty is a good approach.


142-169: Consistent test structure with appropriate assertions.

.github/actions/upload-release-assets/scripts/upload_release_assets.py (1)

1-330: Excellent implementation—all past review concerns resolved.

The code demonstrates high quality throughout:

  • All dependencies correctly specified (syspath-hack present, cyclopts version updated).
  • Boolean coercion properly centralised to bool_utils.
  • Dry-run output accurately reflects the clobber parameter.
  • _is_candidate uses straightforward if statements rather than unnecessary pattern matching.
  • _write_output defensively creates parent directories before writing.
  • Comprehensive error handling for discovery and upload failures.
  • Clear separation of concerns with well-focused helper functions.
  • Thorough documentation and appropriate type hints.

The implementation is production-ready.

Comment thread tests/workflows/test_action_behaviors.py Outdated
Replace direct re.search calls with the shared _assert_log_patterns
helper for consistency. Remove unnecessary capturing group from the
uploaded-count pattern (\d+ instead of (\d+)).

🤖 Generated with [Claude Code](https://claude.com/claude-code)

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
@leynos
Copy link
Copy Markdown
Owner Author

leynos commented Dec 29, 2025

@coderabbitai review

@coderabbitai
Copy link
Copy Markdown
Contributor

coderabbitai Bot commented Dec 29, 2025

✅ Actions performed

Review triggered.

Note: CodeRabbit is an incremental review system and does not re-review already reviewed commits. This command is applicable only when automatic reviews are paused.

@leynos leynos merged commit 9b84b9f into main Dec 29, 2025
17 checks passed
@leynos leynos deleted the import-shared-actions-from-netsuke branch December 29, 2025 18:17
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant