Skip to content

Implement stdlib network and command helpers#187

Open
leynos wants to merge 13 commits intomainfrom
codex/implement-network-and-command-functions
Open

Implement stdlib network and command helpers#187
leynos wants to merge 13 commits intomainfrom
codex/implement-network-and-command-functions

Conversation

@leynos
Copy link
Owner

@leynos leynos commented Oct 9, 2025

Summary

  • add MiniJinja stdlib helpers for shell/grep commands that mark templates impure and surface actionable errors
  • provide a fetch helper with optional caching and expose the state handle while registering the stdlib
  • cover the new behaviour with rstest suites, cucumber features, and updated design documentation and roadmap

Testing

  • make lint
  • make test
  • cargo test --test cucumber

https://chatgpt.com/codex/tasks/task_e_68e7d0a7807c83228d0dbf3ddd828811

Copy link
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

@coderabbitai
Copy link
Contributor

coderabbitai bot commented Oct 9, 2025

Note

Reviews paused

Use the following commands to manage reviews:

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

Caution

Review failed

Failed to post review comments

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

  • New Features
    • Added shell and grep template filters to run external commands.
    • Introduced fetch function to retrieve remote content with optional caching.
    • Clear error reporting for command failures and network issues.
  • Documentation
    • Expanded design details for runtime behaviour and release staging.
    • Updated roadmap to mark Template Stdlib and network/command items complete.
  • Tests
    • Extensive coverage for new filters, fetch behaviour, and CLI/action flows.
  • Chores
    • New composite GitHub Action for release staging with TOML config.
    • Updated release workflows and configuration.

Walkthrough

Introduce StdlibState to track impurity; add platform-aware shell and grep template filters that execute external commands and mark templates impure; add fetch function with optional SHA‑256 on‑disk caching using ureq; update stdlib registration to return StdlibState; update documentation, tests, Cucumber steps and CI staging tooling; add ureq and wait-timeout dependencies.

Changes

Cohort / File(s) Summary
Dependencies
Cargo.toml
Add ureq = "2.10.5" and wait-timeout = "0.2" to [dependencies].
Stdlib core & registration
src/stdlib/mod.rs, src/manifest.rs
Add StdlibState (wraps Arc<AtomicBool>); change register to return StdlibState; manifest captures returned state.
Command filters (shell / grep)
src/stdlib/command.rs
Add shell and grep filters: platform-aware shell/program invocation, argument quoting, stdin piping, stdout capture, robust error mapping to template errors and impurity flagging.
Network fetch function
src/stdlib/network.rs
Add fetch(url, kwargs) with optional cache and cache_dir; compute SHA‑256 cache key; on miss do HTTP GET via ureq; read/write cache; return text or raw bytes as template Value; map errors to template errors.
Docs & roadmap
docs/netsuke-design.md, docs/roadmap.md
Expand implementation details for fetch, caching, shell/grep, impurity semantics; mark Template Stdlib and network/command items complete.
Test support utilities
tests/std_filter_tests/support.rs, test_support/src/command_helper.rs, test_support/src/lib.rs
Add stdlib_env_with_state() -> (Environment<'static>, StdlibState) and make stdlib_env() delegate to it; add test helpers to compile small Rust helper binaries and expose command_helper module.
Tests — environment refactor
tests/std_filter_tests/hash_filters.rs, tests/std_filter_tests/io_filters.rs
Replace direct Environment::new() + stdlib::register with stdlib_env() helper.
New command tests
tests/std_filter_tests/command_filters.rs
Add extensive tests for shell/grep: success, failure, timeouts (Unix), Windows-specific behaviour, impurity tracking and error assertions.
New network tests
tests/std_filter_tests/network_functions.rs, tests/std_filter_tests.rs
Add fetch tests: download, caching across server restarts, UTF‑8 vs raw bytes, and network error reporting; extend test wiring.
Cucumber world & steps
tests/cucumber.rs, tests/steps/stdlib_steps.rs, tests/features/stdlib.feature
Extend CliWorld with StdlibState, HTTP server handle and URL; add server spawn/shutdown helpers; add steps and scenarios for shell/grep/fetch and purity assertions.
Stdlib state usage in tests
tests/std_filter_tests/support.rs, tests/std_filter_tests/*
Tests capture and check impurity via returned StdlibState.
CI & staging tooling
.github/actions/stage/**, .github/workflows/release.yml, scripts under .github/actions/stage/scripts/
Add a composite GitHub Action to stage artefacts driven by TOML config; add staging package modules (config, staging, template/glob/utils, checksum, github_output, fs_utils, environment, errors); add tests for action and CLI; remove older Python stage scripts and refactor workflow to use new action.
Python tests & helpers
tests_python/*
Add fixtures and tests for the staging CLI and staging internals; add helpers to write workspace inputs and decode GitHub output file; remove some legacy tests replaced by new ones.
Misc test visibility & module exports
tests/steps/mod.rs, test_support/src/lib.rs
Make stdlib_steps pub(crate); expose command_helper module.

Sequence Diagram(s)

sequenceDiagram
  autonumber
  actor T as Template Renderer
  participant E as MiniJinja Environment
  participant S as Stdlib::command
  participant Sh as Platform Shell
  participant P as Child Process

  T->>E: apply filter "shell" / "grep"
  E->>S: execute_shell/execute_grep(value, args)
  note right of S #e6f4ff: Set impurity flag (StdlibState)
  S->>Sh: spawn shell/program with quoted args
  Sh->>P: run command, pipe stdin
  alt success
    P-->>S: stdout bytes
    S-->>E: Value from bytes
    E-->>T: Rendered output
  else failure
    P-->>S: non-zero exit or IO error
    S-->>E: Template Error (InvalidOperation)
    E-->>T: Propagate error
  end
Loading
sequenceDiagram
  autonumber
  actor T as Template Renderer
  participant E as MiniJinja Environment
  participant N as Stdlib::network::fetch
  participant C as Cache (.netsuke/fetch)
  participant H as HTTP Client (ureq)

  T->>E: call fetch(url, cache?, cache_dir?)
  E->>N: fetch(url, kwargs)
  alt cache enabled
    N->>C: read(key=sha256(url))
    alt hit
      C-->>N: cached bytes
      N-->>E: Value (string or bytes)
    else miss
      N->>H: GET url
      H-->>N: response bytes or error
      alt success
        N->>C: write(key, bytes)
        C-->>N: ok
        N-->>E: Value
      else failure
        N-->>E: Template Error (InvalidOperation)
      end
    end
  else cache disabled
    N->>H: GET url
    H-->>N: bytes or error
    N-->>E: Value or Error
  end
  E-->>T: Result or Error
Loading

Estimated code review effort

🎯 4 (Complex) | ⏱️ ~60 minutes

Poem

Shells whisper, fetches roam afar,
SHA‑keys tuck responses in a jar;
StdlibState raises the impure flag high,
Tests spin servers, helpers compile nearby;
Netsuke grows, with commands and cache awry. 🐚✨

Pre-merge checks and finishing touches and finishing touches

✅ Passed checks (3 passed)
Check name Status Explanation
Title Check ✅ Passed The title accurately and concisely describes the primary change by highlighting the addition of network (fetch) and command (shell/grep) helpers to the MiniJinja standard library.
Description Check ✅ Passed The description directly outlines the introduction of shell, grep and fetch helpers with caching and impurity tracking, and references the associated test suites and documentation updates, matching the changeset.
Docstring Coverage ✅ Passed Docstring coverage is 80.95% 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 codex/implement-network-and-command-functions
✨ 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 codex/implement-network-and-command-functions

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

Copy link

@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.

ℹ️ 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

Copy link
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: CodeRabbit UI

Review profile: ASSERTIVE

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between 9d215f7 and e3a8f80.

⛔ Files ignored due to path filters (1)
  • Cargo.lock is excluded by !**/*.lock
📒 Files selected for processing (16)
  • Cargo.toml (1 hunks)
  • docs/netsuke-design.md (1 hunks)
  • docs/roadmap.md (2 hunks)
  • src/manifest.rs (1 hunks)
  • src/stdlib/command.rs (1 hunks)
  • src/stdlib/mod.rs (3 hunks)
  • src/stdlib/network.rs (1 hunks)
  • tests/cucumber.rs (3 hunks)
  • tests/features/stdlib.feature (1 hunks)
  • tests/std_filter_tests.rs (1 hunks)
  • tests/std_filter_tests/command_filters.rs (1 hunks)
  • tests/std_filter_tests/hash_filters.rs (4 hunks)
  • tests/std_filter_tests/io_filters.rs (4 hunks)
  • tests/std_filter_tests/network_functions.rs (1 hunks)
  • tests/std_filter_tests/support.rs (2 hunks)
  • tests/steps/stdlib_steps.rs (6 hunks)
🧰 Additional context used
📓 Path-based instructions (6)
Cargo.toml

📄 CodeRabbit inference engine (AGENTS.md)

Cargo.toml: Use explicit version ranges in Cargo.toml and keep dependencies up to date
All dependencies must use SemVer caret requirements (e.g., "1.2.3")
Forbid wildcard (*) and open-ended (>=) version specifiers; use ~ only with documented, specific reasons

Files:

  • Cargo.toml
docs/**/*.md

📄 CodeRabbit inference engine (AGENTS.md)

docs/**/*.md: Use docs/ markdown as the reference for requirements, dependencies, and architectural decisions
Proactively update docs/ when decisions, requirements, or dependencies change
Documentation must use en-GB-oxendict spelling and grammar (LICENSE name unchanged)

Files:

  • docs/netsuke-design.md
  • docs/roadmap.md
**/*.md

📄 CodeRabbit inference engine (AGENTS.md)

**/*.md: Validate Markdown with make markdownlint
Run make fmt after doc changes to format Markdown and fix tables
Validate Mermaid diagrams in Markdown using make nixie
Wrap paragraphs and bullet points at 80 columns
Wrap code blocks at 120 columns
Do not wrap tables and headings
Use dashes (-) for list bullets
Use GitHub-flavoured Markdown footnotes ([^1]) for references and footnotes

Files:

  • docs/netsuke-design.md
  • docs/roadmap.md

⚙️ CodeRabbit configuration file

**/*.md: * Avoid 2nd person or 1st person pronouns ("I", "you", "we")

  • Use en-GB-oxendict (-ize / -yse / -our) spelling and grammar
  • Headings must not be wrapped.
  • Documents must start with a level 1 heading
  • Headings must correctly increase or decrease by no more than one level at a time
  • Use GitHub-flavoured Markdown style for footnotes and endnotes.
  • Numbered footnotes must be numbered by order of appearance in the document.

Files:

  • docs/netsuke-design.md
  • docs/roadmap.md
docs/**/*.{rs,md}

📄 CodeRabbit inference engine (docs/rust-doctest-dry-guide.md)

docs/**/*.{rs,md}: In fenced documentation code blocks, specify the language as rust (```rust)
Use assert!, assert_eq!, and assert_ne! in doctests to verify outcomes rather than relying on panic-free execution alone
Avoid using .unwrap() or .expect() in examples; handle errors idiomatically
For fallible examples, write an explicit fn main() -> Result<...> and hide boilerplate with # lines
Alternatively, use the (()) shorthand at the end of a code block (no whitespace) to create an implicit Result-returning main
Use hidden lines (#) to hide setup, main wrappers, and non-essential use statements in doctests
Prefer no_run over ignore for examples with side effects (e.g., network, filesystem)
Use should_panic for examples intended to panic; the test should fail if it does not panic
Use compile_fail for examples that should not compile, but recognize its brittleness across compiler versions
Use edition20xx (e.g., edition2018, edition2021) on code fences when demonstrating edition-specific syntax
Avoid relying solely on #[cfg(feature = "...")] inside doctest code blocks, which can yield misleading empty-ok tests when the feature is disabled

Files:

  • docs/netsuke-design.md
  • docs/roadmap.md
**/*.rs

📄 CodeRabbit inference engine (AGENTS.md)

**/*.rs: Keep functions small with single responsibility; apply command-query separation
Function documentation must include clear examples; test documentation should omit redundant examples
No single Rust code file may exceed 400 lines; split long switches/dispatch tables; move large test data to external files
Disallow Clippy warnings; code must be clean under clippy
Fix warnings emitted during tests in code, do not silence them
Extract helper functions when a function is too long; maintain separation of concerns and CQRS
Group many related parameters into well-named structs
Consider using Arc for large error values to reduce data returned
Every module must begin with a module-level //! comment describing purpose and utility
Document public APIs with Rustdoc /// comments so cargo doc can generate docs
Prefer immutable data; avoid unnecessary mut bindings
Use Result for error handling instead of panicking where feasible
Avoid unsafe code unless absolutely necessary; document any usage clearly
Place function attributes after doc comments
Do not use return in single-line functions
Use predicate functions for conditionals with more than two branches
Do not silence lints except as a last resort
When suppressing lints, scope narrowly and include a clear reason
Prefer #[expect(...)] over #[allow(...)] for lint handling
When functions are unused for certain features, gate with #[cfg]/#[cfg_attr]
Prefer .expect() over .unwrap()
Use concat!() for long string literals instead of escaping newlines with backslashes
Prefer single-line function bodies where appropriate (e.g., pub fn new(id: u64) -> Self { Self(id) })
Use cap-std for filesystem operations; avoid std::fs directly
Use camino for path handling; avoid std::path::PathBuf directly
Prefer semantic error enums deriving std::error::Error via thiserror for inspectable conditions
Use opaque eyre::Report only at the app boundary; do not expose in public APIs
Never export opaque error types from libraries; convert to domain...

Files:

  • tests/cucumber.rs
  • tests/std_filter_tests/network_functions.rs
  • tests/steps/stdlib_steps.rs
  • src/stdlib/network.rs
  • src/stdlib/mod.rs
  • tests/std_filter_tests/command_filters.rs
  • tests/std_filter_tests.rs
  • tests/std_filter_tests/io_filters.rs
  • src/manifest.rs
  • tests/std_filter_tests/support.rs
  • src/stdlib/command.rs
  • tests/std_filter_tests/hash_filters.rs

⚙️ CodeRabbit configuration file

**/*.rs: * Seek to keep the cyclomatic complexity of functions no more than 12.

  • Adhere to single responsibility and CQRS

  • Place function attributes after doc comments.

  • Do not use return in single-line functions.

  • Move conditionals with >2 branches into a predicate function.

  • Avoid unsafe unless absolutely necessary.

  • Every module must begin with a //! doc comment that explains the module's purpose and utility.

  • Comments and docs must follow en-GB-oxendict (-ize / -yse / -our) spelling and grammar

  • Lints must not be silenced except as a last resort.

    • #[allow] is forbidden.
    • Only narrowly scoped #[expect(lint, reason = "...")] is allowed.
    • No lint groups, no blanket or file-wide suppression.
    • Include FIXME: with link if a fix is expected.
  • Where code is only used by specific features, it must be conditionally compiled or a conditional expectation for unused_code applied.

  • Use rstest fixtures for shared setup and to avoid repetition between tests.

  • Replace duplicated tests with #[rstest(...)] parameterised cases.

  • Prefer mockall for mocks/stubs.

  • Prefer .expect() over .unwrap()

  • Ensure that any API or behavioural changes are reflected in the documentation in docs/

  • Ensure that any completed roadmap steps are recorded in the appropriate roadmap in docs/

  • Files must not exceed 400 lines in length

    • Large modules must be decomposed
    • Long match statements or dispatch tables should be decomposed by domain and collocated with targets
    • Large blocks of inline data (e.g., test fixtures, constants or templates) must be moved to external files and inlined at compile-time or loaded at run-time.
  • Environment access (env::set_var and env::remove_var) are always unsafe in Rust 2024 and MUST be marked as such

    • For testing of functionality depending upon environment variables, dependency injection and the mockable crate are the preferred option.
    • If mockable cannot be used, env mutations in...

Files:

  • tests/cucumber.rs
  • tests/std_filter_tests/network_functions.rs
  • tests/steps/stdlib_steps.rs
  • src/stdlib/network.rs
  • src/stdlib/mod.rs
  • tests/std_filter_tests/command_filters.rs
  • tests/std_filter_tests.rs
  • tests/std_filter_tests/io_filters.rs
  • src/manifest.rs
  • tests/std_filter_tests/support.rs
  • src/stdlib/command.rs
  • tests/std_filter_tests/hash_filters.rs
tests/**/*.rs

📄 CodeRabbit inference engine (AGENTS.md)

tests/**/*.rs: Write unit and behavioural tests for new functionality; run before and after changes
Use rstest fixtures for shared setup
Replace duplicated tests with #[rstest(...)] parameterised cases
Prefer mockall for mocks/stubs
Mock nondeterministic deps via dependency injection using mockable traits (e.g., Env, Clock); follow docs/reliable-testing-in-rust-via-dependency-injection.md

Files:

  • tests/cucumber.rs
  • tests/std_filter_tests/network_functions.rs
  • tests/steps/stdlib_steps.rs
  • tests/std_filter_tests/command_filters.rs
  • tests/std_filter_tests.rs
  • tests/std_filter_tests/io_filters.rs
  • tests/std_filter_tests/support.rs
  • tests/std_filter_tests/hash_filters.rs
🧬 Code graph analysis (9)
tests/std_filter_tests/network_functions.rs (2)
tests/std_filter_tests/support.rs (1)
  • stdlib_env_with_state (22-26)
src/stdlib/mod.rs (1)
  • is_impure (39-41)
tests/steps/stdlib_steps.rs (2)
src/stdlib/command.rs (1)
  • register (33-51)
src/stdlib/mod.rs (2)
  • register (69-78)
  • is_impure (39-41)
src/stdlib/mod.rs (2)
src/stdlib/command.rs (1)
  • register (33-51)
src/stdlib/network.rs (1)
  • register_functions (18-20)
tests/std_filter_tests/command_filters.rs (2)
tests/std_filter_tests/support.rs (1)
  • stdlib_env_with_state (22-26)
src/stdlib/mod.rs (1)
  • is_impure (39-41)
tests/std_filter_tests/io_filters.rs (1)
tests/std_filter_tests/support.rs (3)
  • filter_workspace (34-45)
  • render (47-58)
  • stdlib_env (28-31)
src/manifest.rs (2)
src/stdlib/command.rs (1)
  • register (33-51)
src/stdlib/mod.rs (1)
  • register (69-78)
tests/std_filter_tests/support.rs (2)
src/stdlib/command.rs (1)
  • register (33-51)
src/stdlib/mod.rs (1)
  • register (69-78)
src/stdlib/command.rs (1)
src/stdlib/mod.rs (1)
  • register (69-78)
tests/std_filter_tests/hash_filters.rs (1)
tests/std_filter_tests/support.rs (3)
  • filter_workspace (34-45)
  • register_template (12-20)
  • stdlib_env (28-31)
🪛 GitHub Actions: CI
tests/std_filter_tests/command_filters.rs

[error] 35-35: test failed: error should mention command exit status: invalid operation: shell command 'false' in template 'shell_fail' failed: Broken pipe (os error 32)

🔍 Remote MCP Deepwiki

I've gathered design details for the new stdlib network & command helpers and the functions implemented in the stdlib modules. Below is a concise summary of additional relevant context for the PR review:

Summary of relevant context:

  • docs/netsuke-design.md details:

    • fetch(url, cache=false, cache_dir) function retrieves remote content with optional on-disk caching by SHA-256 URL digest, caches under .netsuke/fetch.
    • shell and grep filters spawn a POSIX shell with single-quoted args via shell-quote, mark templates impure in StdlibState.
  • src/stdlib/network.rs:

    • Implements register_functions(env) -> () to register fetch helper.
    • fetch(url, kwargs): validates args, optionally reads/writes cache, performs HTTP GET via ureq, maps errors to TemplateErrors, returns bytes or UTF-8 string.
  • src/stdlib/command.rs:

    • Adds shell and grep filters via register(env, impure_flag) marking StdlibState impure_flag on use.
    • execute_shell(state, value, command): pipes input to command, captures stdout, maps failures.
    • execute_grep(state, value, pattern, flags): builds grep command, parses flags, filters bytes.
    • Quoting via quote(arg) and format_command, runs via platform shell (run_command).
    • Error mapping with CommandFailure and command_error.

This context clarifies the implemented helpers' behavior and design considerations (security, caching, impurity), aiding review of the new stdlib modules.

⏰ 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: release / build-linux (aarch64-unknown-linux-gnu, linux-arm64, arm64)
  • GitHub Check: release / build-macos (aarch64-apple-darwin, macos-14, macos-arm64, arm64)
  • GitHub Check: release / build-macos (x86_64-apple-darwin, macos-13, macos-x86_64, x86_64)
  • GitHub Check: release / build-windows (x86_64-pc-windows-msvc, amd64, x64)
  • GitHub Check: release / build-linux (x86_64-unknown-linux-gnu, linux-amd64, amd64)
  • GitHub Check: release / build-windows (aarch64-pc-windows-msvc, arm64, arm64)
🔇 Additional comments (13)
src/manifest.rs (1)

78-78: LGTM!

Capturing the return value with an underscore prefix correctly suppresses the unused-value warning whilst maintaining code clarity.

docs/roadmap.md (2)

157-157: LGTM!

The Template Standard Library has been implemented with the file-system tests, path filters, collection filters, network functions, command filters, and time helpers as documented in netsuke-design.md.


168-168: LGTM!

The network and command functions/filters (fetch, shell, grep) have been implemented with impurity tracking via StdlibState, as confirmed by the test suite and documentation updates.

tests/std_filter_tests.rs (2)

3-4: LGTM!

The command_filters module is correctly wired following the established pattern for test organization.


9-10: LGTM!

The network_functions module is correctly wired following the established pattern for test organization.

tests/features/stdlib.feature (2)

75-88: LGTM!

The shell and grep filter scenarios correctly verify both successful transformations and error handling, with proper impurity tracking assertions.


90-99: LGTM!

The fetch scenarios correctly verify network retrieval behaviour and error handling, with the appropriate purity assertion (fetch is pure, unlike shell/grep).

tests/std_filter_tests/hash_filters.rs (1)

2-2: LGTM!

The refactoring to use stdlib_env() centralises environment setup and reduces duplication across test files, improving maintainability.

Also applies to: 5-5, 51-51, 89-89, 105-105

tests/std_filter_tests/command_filters.rs (3)

6-19: LGTM!

The test correctly verifies that the shell filter transforms input and marks templates impure, with proper state cleanup and assertions.


41-51: LGTM!

The test correctly verifies that the grep filter extracts matching lines and marks templates impure.


53-67: LGTM!

The test correctly verifies that invalid non-string flags are rejected with an appropriate error message.

docs/netsuke-design.md (1)

925-938: LGTM!

The implementation details accurately document the fetch caching mechanism, shell command execution, and impurity tracking, providing valuable context for maintainers and contributors.

Cargo.toml (1)

47-47: Confirm ureq 2.10.5 is valid and secure.
Listed on crates.io; no known security advisories.

leynos added 2 commits October 9, 2025 22:58
Run the grep filter directly on Windows to avoid cmd quoting bugs, reuse the binary output buffer when decoding, and ensure cucumber servers shut down cleanly with a non-brittle failure assertion.
Copy link
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

📜 Review details

Configuration used: CodeRabbit UI

Review profile: ASSERTIVE

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between e3a8f80 and ceb2722.

📒 Files selected for processing (4)
  • src/stdlib/command.rs (1 hunks)
  • src/stdlib/network.rs (1 hunks)
  • tests/cucumber.rs (3 hunks)
  • tests/std_filter_tests/command_filters.rs (1 hunks)
🧰 Additional context used
📓 Path-based instructions (2)
**/*.rs

📄 CodeRabbit inference engine (AGENTS.md)

**/*.rs: Keep functions small with single responsibility; apply command-query separation
Function documentation must include clear examples; test documentation should omit redundant examples
No single Rust code file may exceed 400 lines; split long switches/dispatch tables; move large test data to external files
Disallow Clippy warnings; code must be clean under clippy
Fix warnings emitted during tests in code, do not silence them
Extract helper functions when a function is too long; maintain separation of concerns and CQRS
Group many related parameters into well-named structs
Consider using Arc for large error values to reduce data returned
Every module must begin with a module-level //! comment describing purpose and utility
Document public APIs with Rustdoc /// comments so cargo doc can generate docs
Prefer immutable data; avoid unnecessary mut bindings
Use Result for error handling instead of panicking where feasible
Avoid unsafe code unless absolutely necessary; document any usage clearly
Place function attributes after doc comments
Do not use return in single-line functions
Use predicate functions for conditionals with more than two branches
Do not silence lints except as a last resort
When suppressing lints, scope narrowly and include a clear reason
Prefer #[expect(...)] over #[allow(...)] for lint handling
When functions are unused for certain features, gate with #[cfg]/#[cfg_attr]
Prefer .expect() over .unwrap()
Use concat!() for long string literals instead of escaping newlines with backslashes
Prefer single-line function bodies where appropriate (e.g., pub fn new(id: u64) -> Self { Self(id) })
Use cap-std for filesystem operations; avoid std::fs directly
Use camino for path handling; avoid std::path::PathBuf directly
Prefer semantic error enums deriving std::error::Error via thiserror for inspectable conditions
Use opaque eyre::Report only at the app boundary; do not expose in public APIs
Never export opaque error types from libraries; convert to domain...

Files:

  • src/stdlib/network.rs
  • tests/cucumber.rs
  • tests/std_filter_tests/command_filters.rs
  • src/stdlib/command.rs

⚙️ CodeRabbit configuration file

**/*.rs: * Seek to keep the cyclomatic complexity of functions no more than 12.

  • Adhere to single responsibility and CQRS

  • Place function attributes after doc comments.

  • Do not use return in single-line functions.

  • Move conditionals with >2 branches into a predicate function.

  • Avoid unsafe unless absolutely necessary.

  • Every module must begin with a //! doc comment that explains the module's purpose and utility.

  • Comments and docs must follow en-GB-oxendict (-ize / -yse / -our) spelling and grammar

  • Lints must not be silenced except as a last resort.

    • #[allow] is forbidden.
    • Only narrowly scoped #[expect(lint, reason = "...")] is allowed.
    • No lint groups, no blanket or file-wide suppression.
    • Include FIXME: with link if a fix is expected.
  • Where code is only used by specific features, it must be conditionally compiled or a conditional expectation for unused_code applied.

  • Use rstest fixtures for shared setup and to avoid repetition between tests.

  • Replace duplicated tests with #[rstest(...)] parameterised cases.

  • Prefer mockall for mocks/stubs.

  • Prefer .expect() over .unwrap()

  • Ensure that any API or behavioural changes are reflected in the documentation in docs/

  • Ensure that any completed roadmap steps are recorded in the appropriate roadmap in docs/

  • Files must not exceed 400 lines in length

    • Large modules must be decomposed
    • Long match statements or dispatch tables should be decomposed by domain and collocated with targets
    • Large blocks of inline data (e.g., test fixtures, constants or templates) must be moved to external files and inlined at compile-time or loaded at run-time.
  • Environment access (env::set_var and env::remove_var) are always unsafe in Rust 2024 and MUST be marked as such

    • For testing of functionality depending upon environment variables, dependency injection and the mockable crate are the preferred option.
    • If mockable cannot be used, env mutations in...

Files:

  • src/stdlib/network.rs
  • tests/cucumber.rs
  • tests/std_filter_tests/command_filters.rs
  • src/stdlib/command.rs
tests/**/*.rs

📄 CodeRabbit inference engine (AGENTS.md)

tests/**/*.rs: Write unit and behavioural tests for new functionality; run before and after changes
Use rstest fixtures for shared setup
Replace duplicated tests with #[rstest(...)] parameterised cases
Prefer mockall for mocks/stubs
Mock nondeterministic deps via dependency injection using mockable traits (e.g., Env, Clock); follow docs/reliable-testing-in-rust-via-dependency-injection.md

Files:

  • tests/cucumber.rs
  • tests/std_filter_tests/command_filters.rs
🧬 Code graph analysis (2)
tests/std_filter_tests/command_filters.rs (2)
tests/std_filter_tests/support.rs (1)
  • stdlib_env_with_state (22-26)
src/stdlib/mod.rs (1)
  • is_impure (39-41)
src/stdlib/command.rs (1)
src/stdlib/mod.rs (1)
  • register (69-78)
🔍 Remote MCP Ref

Summary of additional concrete facts relevant to review

  • Public API changes

    • New public struct StdlibState with Arc impurity flag; register(...) now returns StdlibState (callers updated in tests) — affects public API and requires consumers to handle the returned state.,
  • New stdlib helpers

    • Command filters: added shell and grep template filters that execute shell commands via a platform shell; they set the StdlibState impure flag when used and convert failures into MiniJinja template errors with detailed context.
    • Network function: added fetch(url, cache=false, cache_dir=...) that performs HTTP GET using the ureq crate and supports optional on-disk caching. Errors are mapped to template errors.
  • Dependency change

    • Cargo.toml adds ureq = "2.10.5" (new runtime dependency for HTTP). Review for license/compat and transitive dependencies.
  • Caching behavior (implementation details)

    • Cache key = SHA-256 hex digest of the URL; cache files stored under a cache directory (defaults to .netsuke/fetch when enabled). Cache read/write handles and error paths implemented; unit test verifies stable cache key generation.
  • Tests and fixtures

    • New/updated tests cover command filters and network functions (including a local HTTP server test, cache persistence across restarts, impurity assertions). Test helpers now expose stdlib_env_with_state() returning (Environment, StdlibState) and tests assert StdlibState.is_impure/reset_impure behavior. Cucumber scenarios and steps added to exercise HTTP server and impurity checks. Tests update teardown to join test HTTP server threads.,
  • Docs and roadmap

    • docs/netsuke-design.md documents the new fetch behavior (ureq usage, SHA-256 cache-keying, .netsuke/fetch), shell/grep quoting and impurity semantics. docs/roadmap.md marks Template Stdlib and network/command tasks as completed.

Potential review focus items (actionable)

  • Verify the public API change (register -> returns StdlibState) is acceptable and documented for downstream callers.
  • Audit ureq addition for license/compat and consider adding to changelog/release notes.
  • Security review of shell/grep filters (command injection risk, quoting correctness, platform differences) and whether their use should be opt-in or documented with warnings.
  • Validate cache path handling (relative vs absolute), file permissions, and error handling around cache corruption.
  • Ensure tests reliably clean up HTTP server threads and that stdlib_state is not leaked across tests.

Sources

  • Repository code and docs searched/read via Ref documentation search and read tools.,
⏰ 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: release / build-macos (x86_64-apple-darwin, macos-13, macos-x86_64, x86_64)
  • GitHub Check: release / build-windows (x86_64-pc-windows-msvc, amd64, x64)
  • GitHub Check: release / build-windows (aarch64-pc-windows-msvc, arm64, arm64)
  • GitHub Check: release / build-linux (x86_64-unknown-linux-gnu, linux-amd64, amd64)
  • GitHub Check: release / build-linux (aarch64-unknown-linux-gnu, linux-arm64, arm64)
  • GitHub Check: build-test (ubuntu-latest, stable)

Copy link
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: 5

♻️ Duplicate comments (2)
src/stdlib/command.rs (1)

88-94: LGTM! Windows quoting issue resolved.

The platform-specific approach correctly addresses the Windows cmd.exe quoting issue flagged in previous reviews. On Windows, grep invokes the programme directly with unquoted arguments. On Unix, the command string uses POSIX quoting through the shell. This ensures correct behaviour on both platforms.

tests/std_filter_tests/command_filters.rs (1)

21-41: LGTM! Brittle assertion fixed.

The test now correctly handles multiple error message formats (lines 36-39), resolving the issue from previous reviews where "Broken pipe (os error 32)" failed the assertion. The broader check for "shell command", "failed", or "error" ensures the test passes for all legitimate failure modes whilst still validating that an error occurred.

📜 Review details

Configuration used: CodeRabbit UI

Review profile: ASSERTIVE

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between e3a8f80 and ceb2722.

📒 Files selected for processing (4)
  • src/stdlib/command.rs (1 hunks)
  • src/stdlib/network.rs (1 hunks)
  • tests/cucumber.rs (3 hunks)
  • tests/std_filter_tests/command_filters.rs (1 hunks)
🧰 Additional context used
📓 Path-based instructions (2)
**/*.rs

📄 CodeRabbit inference engine (AGENTS.md)

**/*.rs: Keep functions small with single responsibility; apply command-query separation
Function documentation must include clear examples; test documentation should omit redundant examples
No single Rust code file may exceed 400 lines; split long switches/dispatch tables; move large test data to external files
Disallow Clippy warnings; code must be clean under clippy
Fix warnings emitted during tests in code, do not silence them
Extract helper functions when a function is too long; maintain separation of concerns and CQRS
Group many related parameters into well-named structs
Consider using Arc for large error values to reduce data returned
Every module must begin with a module-level //! comment describing purpose and utility
Document public APIs with Rustdoc /// comments so cargo doc can generate docs
Prefer immutable data; avoid unnecessary mut bindings
Use Result for error handling instead of panicking where feasible
Avoid unsafe code unless absolutely necessary; document any usage clearly
Place function attributes after doc comments
Do not use return in single-line functions
Use predicate functions for conditionals with more than two branches
Do not silence lints except as a last resort
When suppressing lints, scope narrowly and include a clear reason
Prefer #[expect(...)] over #[allow(...)] for lint handling
When functions are unused for certain features, gate with #[cfg]/#[cfg_attr]
Prefer .expect() over .unwrap()
Use concat!() for long string literals instead of escaping newlines with backslashes
Prefer single-line function bodies where appropriate (e.g., pub fn new(id: u64) -> Self { Self(id) })
Use cap-std for filesystem operations; avoid std::fs directly
Use camino for path handling; avoid std::path::PathBuf directly
Prefer semantic error enums deriving std::error::Error via thiserror for inspectable conditions
Use opaque eyre::Report only at the app boundary; do not expose in public APIs
Never export opaque error types from libraries; convert to domain...

Files:

  • src/stdlib/command.rs
  • tests/cucumber.rs
  • src/stdlib/network.rs
  • tests/std_filter_tests/command_filters.rs

⚙️ CodeRabbit configuration file

**/*.rs: * Seek to keep the cyclomatic complexity of functions no more than 12.

  • Adhere to single responsibility and CQRS

  • Place function attributes after doc comments.

  • Do not use return in single-line functions.

  • Move conditionals with >2 branches into a predicate function.

  • Avoid unsafe unless absolutely necessary.

  • Every module must begin with a //! doc comment that explains the module's purpose and utility.

  • Comments and docs must follow en-GB-oxendict (-ize / -yse / -our) spelling and grammar

  • Lints must not be silenced except as a last resort.

    • #[allow] is forbidden.
    • Only narrowly scoped #[expect(lint, reason = "...")] is allowed.
    • No lint groups, no blanket or file-wide suppression.
    • Include FIXME: with link if a fix is expected.
  • Where code is only used by specific features, it must be conditionally compiled or a conditional expectation for unused_code applied.

  • Use rstest fixtures for shared setup and to avoid repetition between tests.

  • Replace duplicated tests with #[rstest(...)] parameterised cases.

  • Prefer mockall for mocks/stubs.

  • Prefer .expect() over .unwrap()

  • Ensure that any API or behavioural changes are reflected in the documentation in docs/

  • Ensure that any completed roadmap steps are recorded in the appropriate roadmap in docs/

  • Files must not exceed 400 lines in length

    • Large modules must be decomposed
    • Long match statements or dispatch tables should be decomposed by domain and collocated with targets
    • Large blocks of inline data (e.g., test fixtures, constants or templates) must be moved to external files and inlined at compile-time or loaded at run-time.
  • Environment access (env::set_var and env::remove_var) are always unsafe in Rust 2024 and MUST be marked as such

    • For testing of functionality depending upon environment variables, dependency injection and the mockable crate are the preferred option.
    • If mockable cannot be used, env mutations in...

Files:

  • src/stdlib/command.rs
  • tests/cucumber.rs
  • src/stdlib/network.rs
  • tests/std_filter_tests/command_filters.rs
tests/**/*.rs

📄 CodeRabbit inference engine (AGENTS.md)

tests/**/*.rs: Write unit and behavioural tests for new functionality; run before and after changes
Use rstest fixtures for shared setup
Replace duplicated tests with #[rstest(...)] parameterised cases
Prefer mockall for mocks/stubs
Mock nondeterministic deps via dependency injection using mockable traits (e.g., Env, Clock); follow docs/reliable-testing-in-rust-via-dependency-injection.md

Files:

  • tests/cucumber.rs
  • tests/std_filter_tests/command_filters.rs
🧬 Code graph analysis (2)
src/stdlib/command.rs (1)
src/stdlib/mod.rs (1)
  • register (69-78)
tests/std_filter_tests/command_filters.rs (2)
tests/std_filter_tests/support.rs (1)
  • stdlib_env_with_state (22-26)
src/stdlib/mod.rs (1)
  • is_impure (39-41)
🔍 Remote MCP

Concise additional facts relevant to review

  • New runtime state: StdlibState (pub) now exposes impure tracking (is_impure, reset_impure, impure_flag) and is returned from stdlib::register — callers must handle/propagate this state where needed (tests now use stdlib_env_with_state).
  • Command filters (shell, grep):
    • Spawn platform shell with quoted args (quote/format_command), pipe input bytes to subprocess, capture stdout, and map failures into Template errors with exit/status context. Using an impure_flag to mark templates impure when used.
    • grep supports flags parsing (sequences or single string); invalid flag types produce InvalidOperation errors.
  • Network fetch:
    • fetch(url, cache=false, cache_dir) implemented with ureq (dependency added in Cargo.toml). Optional on-disk caching keyed by SHA-256 hex of URL; cache directory created under supplied path or default (.netsuke/fetch). Cache read/write use ambient authority and return UTF-8 string when possible, else raw bytes.
    • Errors from HTTP or cache IO map to Template InvalidOperation errors with explanatory messages.
  • Tests and fixtures:
    • New unit/integration tests added:
      • tests/std_filter_tests/{command_filters.rs,network_functions.rs} exercise shell/grep and fetch (including cache behavior).
      • Cucumber feature scenarios added for shell/grep/fetch and a local HTTP test server helper (spawn_http_server) used in steps and tests.
      • Test helper stdlib_env_with_state returns (Environment, StdlibState); stdlib_env() now discards the state.
    • CliWorld in cucumber tests now stores stdlib_state, http_server handle, and stdlib_url; Drop impl joins server thread when present.
  • Docs/roadmap updated to reflect implemented features and record design details (fetch cache key algorithm, cache location, impurity semantics for shell/grep, and shell quoting behavior).
  • Public API changes to review for compatibility:
    • Added public struct StdlibState and its methods.
    • Changed signature: pub fn register(env: &mut Environment<'_>) -> StdlibState (was unit-like).
    • Added pub(crate) fn register_functions(env: &mut Environment<'_>) for network registration.
  • Dependency change:
    • Added ureq = "2.10.5" to Cargo.toml (network fetch).

Sources used for this summary: provided PR context and diffs (files and tests listed in the PR).

⏰ 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: release / build-macos (x86_64-apple-darwin, macos-13, macos-x86_64, x86_64)
  • GitHub Check: release / build-windows (x86_64-pc-windows-msvc, amd64, x64)
  • GitHub Check: release / build-windows (aarch64-pc-windows-msvc, arm64, arm64)
  • GitHub Check: release / build-linux (x86_64-unknown-linux-gnu, linux-amd64, amd64)
  • GitHub Check: build-test (ubuntu-latest, stable)
🔇 Additional comments (4)
src/stdlib/command.rs (3)

1-7: LGTM!

The module documentation clearly explains purpose, behaviour, and the impurity tracking mechanism.


126-144: Verify windows_quote correctness for cmd.exe.

The quote function uses shell_quote::windows::quote to escape arguments for cmd.exe on Windows. Verify that this function produces output compatible with cmd.exe's parsing rules, as Windows command-line quoting has complex edge cases around special characters (e.g., ^, %, !). If shell_quote's Windows implementation targets CreateProcess rather than cmd.exe specifically, the quoting may not be safe.

Run the following script to check the shell_quote documentation and verify cmd.exe compatibility:

Does the Rust shell_quote crate's windows::quote function produce output compatible with cmd.exe's parsing rules, or does it target CreateProcess? What special characters does it handle?

240-253: LGTM!

The CommandFailure enum and From<io::Error> implementation provide clear error categorisation and good ergonomics for error propagation.

tests/std_filter_tests/command_filters.rs (1)

6-19: LGTM!

The test correctly verifies that the shell filter marks templates impure and produces the expected output. The setup using stdlib_env_with_state and explicit impurity reset ensures clean test state.

Wire the stdlib impurity flag into fetch, harden cache directory handling for Windows paths, and document the shell security expectations. Add Windows-only command filter stubs plus fetch impurity assertions in unit and cucumber tests to cover the new behaviour.
Copy link
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: 3

📜 Review details

Configuration used: CodeRabbit UI

Review profile: ASSERTIVE

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between ceb2722 and d3aec13.

📒 Files selected for processing (7)
  • docs/netsuke-design.md (1 hunks)
  • src/stdlib/command.rs (1 hunks)
  • src/stdlib/mod.rs (3 hunks)
  • src/stdlib/network.rs (1 hunks)
  • tests/features/stdlib.feature (1 hunks)
  • tests/std_filter_tests/command_filters.rs (1 hunks)
  • tests/std_filter_tests/network_functions.rs (1 hunks)
🧰 Additional context used
📓 Path-based instructions (5)
**/*.rs

📄 CodeRabbit inference engine (AGENTS.md)

**/*.rs: Keep functions small with single responsibility; apply command-query separation
Function documentation must include clear examples; test documentation should omit redundant examples
No single Rust code file may exceed 400 lines; split long switches/dispatch tables; move large test data to external files
Disallow Clippy warnings; code must be clean under clippy
Fix warnings emitted during tests in code, do not silence them
Extract helper functions when a function is too long; maintain separation of concerns and CQRS
Group many related parameters into well-named structs
Consider using Arc for large error values to reduce data returned
Every module must begin with a module-level //! comment describing purpose and utility
Document public APIs with Rustdoc /// comments so cargo doc can generate docs
Prefer immutable data; avoid unnecessary mut bindings
Use Result for error handling instead of panicking where feasible
Avoid unsafe code unless absolutely necessary; document any usage clearly
Place function attributes after doc comments
Do not use return in single-line functions
Use predicate functions for conditionals with more than two branches
Do not silence lints except as a last resort
When suppressing lints, scope narrowly and include a clear reason
Prefer #[expect(...)] over #[allow(...)] for lint handling
When functions are unused for certain features, gate with #[cfg]/#[cfg_attr]
Prefer .expect() over .unwrap()
Use concat!() for long string literals instead of escaping newlines with backslashes
Prefer single-line function bodies where appropriate (e.g., pub fn new(id: u64) -> Self { Self(id) })
Use cap-std for filesystem operations; avoid std::fs directly
Use camino for path handling; avoid std::path::PathBuf directly
Prefer semantic error enums deriving std::error::Error via thiserror for inspectable conditions
Use opaque eyre::Report only at the app boundary; do not expose in public APIs
Never export opaque error types from libraries; convert to domain...

Files:

  • tests/std_filter_tests/command_filters.rs
  • src/stdlib/mod.rs
  • src/stdlib/network.rs
  • tests/std_filter_tests/network_functions.rs
  • src/stdlib/command.rs

⚙️ CodeRabbit configuration file

**/*.rs: * Seek to keep the cyclomatic complexity of functions no more than 12.

  • Adhere to single responsibility and CQRS

  • Place function attributes after doc comments.

  • Do not use return in single-line functions.

  • Move conditionals with >2 branches into a predicate function.

  • Avoid unsafe unless absolutely necessary.

  • Every module must begin with a //! doc comment that explains the module's purpose and utility.

  • Comments and docs must follow en-GB-oxendict (-ize / -yse / -our) spelling and grammar

  • Lints must not be silenced except as a last resort.

    • #[allow] is forbidden.
    • Only narrowly scoped #[expect(lint, reason = "...")] is allowed.
    • No lint groups, no blanket or file-wide suppression.
    • Include FIXME: with link if a fix is expected.
  • Where code is only used by specific features, it must be conditionally compiled or a conditional expectation for unused_code applied.

  • Use rstest fixtures for shared setup and to avoid repetition between tests.

  • Replace duplicated tests with #[rstest(...)] parameterised cases.

  • Prefer mockall for mocks/stubs.

  • Prefer .expect() over .unwrap()

  • Ensure that any API or behavioural changes are reflected in the documentation in docs/

  • Ensure that any completed roadmap steps are recorded in the appropriate roadmap in docs/

  • Files must not exceed 400 lines in length

    • Large modules must be decomposed
    • Long match statements or dispatch tables should be decomposed by domain and collocated with targets
    • Large blocks of inline data (e.g., test fixtures, constants or templates) must be moved to external files and inlined at compile-time or loaded at run-time.
  • Environment access (env::set_var and env::remove_var) are always unsafe in Rust 2024 and MUST be marked as such

    • For testing of functionality depending upon environment variables, dependency injection and the mockable crate are the preferred option.
    • If mockable cannot be used, env mutations in...

Files:

  • tests/std_filter_tests/command_filters.rs
  • src/stdlib/mod.rs
  • src/stdlib/network.rs
  • tests/std_filter_tests/network_functions.rs
  • src/stdlib/command.rs
tests/**/*.rs

📄 CodeRabbit inference engine (AGENTS.md)

tests/**/*.rs: Write unit and behavioural tests for new functionality; run before and after changes
Use rstest fixtures for shared setup
Replace duplicated tests with #[rstest(...)] parameterised cases
Prefer mockall for mocks/stubs
Mock nondeterministic deps via dependency injection using mockable traits (e.g., Env, Clock); follow docs/reliable-testing-in-rust-via-dependency-injection.md

Files:

  • tests/std_filter_tests/command_filters.rs
  • tests/std_filter_tests/network_functions.rs
docs/**/*.md

📄 CodeRabbit inference engine (AGENTS.md)

docs/**/*.md: Use docs/ markdown as the reference for requirements, dependencies, and architectural decisions
Proactively update docs/ when decisions, requirements, or dependencies change
Documentation must use en-GB-oxendict spelling and grammar (LICENSE name unchanged)

Files:

  • docs/netsuke-design.md
**/*.md

📄 CodeRabbit inference engine (AGENTS.md)

**/*.md: Validate Markdown with make markdownlint
Run make fmt after doc changes to format Markdown and fix tables
Validate Mermaid diagrams in Markdown using make nixie
Wrap paragraphs and bullet points at 80 columns
Wrap code blocks at 120 columns
Do not wrap tables and headings
Use dashes (-) for list bullets
Use GitHub-flavoured Markdown footnotes ([^1]) for references and footnotes

Files:

  • docs/netsuke-design.md

⚙️ CodeRabbit configuration file

**/*.md: * Avoid 2nd person or 1st person pronouns ("I", "you", "we")

  • Use en-GB-oxendict (-ize / -yse / -our) spelling and grammar
  • Headings must not be wrapped.
  • Documents must start with a level 1 heading
  • Headings must correctly increase or decrease by no more than one level at a time
  • Use GitHub-flavoured Markdown style for footnotes and endnotes.
  • Numbered footnotes must be numbered by order of appearance in the document.

Files:

  • docs/netsuke-design.md
docs/**/*.{rs,md}

📄 CodeRabbit inference engine (docs/rust-doctest-dry-guide.md)

docs/**/*.{rs,md}: In fenced documentation code blocks, specify the language as rust (```rust)
Use assert!, assert_eq!, and assert_ne! in doctests to verify outcomes rather than relying on panic-free execution alone
Avoid using .unwrap() or .expect() in examples; handle errors idiomatically
For fallible examples, write an explicit fn main() -> Result<...> and hide boilerplate with # lines
Alternatively, use the (()) shorthand at the end of a code block (no whitespace) to create an implicit Result-returning main
Use hidden lines (#) to hide setup, main wrappers, and non-essential use statements in doctests
Prefer no_run over ignore for examples with side effects (e.g., network, filesystem)
Use should_panic for examples intended to panic; the test should fail if it does not panic
Use compile_fail for examples that should not compile, but recognize its brittleness across compiler versions
Use edition20xx (e.g., edition2018, edition2021) on code fences when demonstrating edition-specific syntax
Avoid relying solely on #[cfg(feature = "...")] inside doctest code blocks, which can yield misleading empty-ok tests when the feature is disabled

Files:

  • docs/netsuke-design.md
🧬 Code graph analysis (4)
tests/std_filter_tests/command_filters.rs (3)
tests/std_filter_tests/support.rs (1)
  • stdlib_env_with_state (22-26)
src/stdlib/mod.rs (1)
  • is_impure (39-41)
test_support/src/env_lock.rs (1)
  • acquire (17-21)
src/stdlib/mod.rs (2)
src/stdlib/command.rs (1)
  • register (43-61)
src/stdlib/network.rs (1)
  • register_functions (24-28)
tests/std_filter_tests/network_functions.rs (2)
tests/std_filter_tests/support.rs (1)
  • stdlib_env_with_state (22-26)
src/stdlib/mod.rs (1)
  • is_impure (39-41)
src/stdlib/command.rs (1)
src/stdlib/mod.rs (1)
  • register (69-79)
🔍 Remote MCP Deepwiki

Summary of additional facts relevant to review

  • Public API changes

    • stdlib::register now returns StdlibState (holds Arc) and exposes methods is_impure, reset_impure, impure_flag — callers must capture/propagate state.
  • New stdlib features and behavior

    • Command filters: new shell and grep filters execute external commands, mark templates impure on first use, perform platform-aware quoting and execution, and map subprocess failures into Template errors. Review arg quoting, Windows path/program handling, and CommandFailure → Template error mapping.
    • Network fetch: fetch(url, cache=false, cache_dir) implemented using ureq (uvep added to Cargo), optional on-disk caching keyed by SHA-256(hex(url)), default cache at .netsuke/fetch, reads/writes use ambient authority and return UTF-8 when possible; remote fetches or cache writes mark stdlib impure. Validate HTTP error mapping and cache error handling.
  • Dependency change

    • Added ureq = "2.10.5" for HTTP fetch support (affects licensing/attack surface).
  • Tests and fixtures added/updated

    • New unit/integration tests: command_filters.rs, network_functions.rs; cucumber feature scenarios for shell/grep/fetch; test helpers stdlib_env_with_state and stdlib_env(); CliWorld in cucumber now holds stdlib_state, http_server handle and stdlib_url and Drop joins server thread. Ensure CI runs cover these new tests (cargo test --test cucumber).
  • Docs/roadmap updates

    • docs/netsuke-design.md documents impurity semantics, cache-key algorithm (SHA-256 hex of URL), cache location and ambient-authority rationale, shell quoting strategy, and that use of external commands marks stdlib impure.
  • Areas to focus in review

    • Correct propagation and lifetime/Clone semantics of StdlibState (thread-safety via Arc).
    • Security of shell argument quoting and final command string validation (ensure no injection bypass, shlex validation where applicable).
    • Windows-specific command execution paths (run_program vs shell invocation) and test coverage for Windows stubs.
    • Robustness of cache path resolution (absolute vs relative, Windows drive prefixes) and error handling when writing cache (permissions/partial writes).
    • Error messages mapped to Template InvalidOperation — verify they are actionable and not leaking sensitive info.
    • CI/test ergonomics: ensure new cucumber tests and local HTTP server teardown/join logic are reliable under CI timeouts.

Sources

  • Project docs and implementation overview pages (includes stdlib, network, command, tests, roadmap) retrieved from repository wiki/documentation.
⏰ 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). (4)
  • GitHub Check: release / build-macos (x86_64-apple-darwin, macos-13, macos-x86_64, x86_64)
  • GitHub Check: release / build-windows (x86_64-pc-windows-msvc, amd64, x64)
  • GitHub Check: release / build-windows (aarch64-pc-windows-msvc, arm64, arm64)
  • GitHub Check: build-test (ubuntu-latest, stable)

Copy link
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: 10

Caution

Some comments are outside the diff and can’t be posted inline due to platform limitations.

⚠️ Outside diff range comments (1)
tests/steps/stdlib_steps.rs (1)

205-220: Guard environment mutation with a shared lock

Wrap HOME/USERPROFILE mutation in a global env lock held for the scenario lifetime (store the guard in CliWorld) to satisfy the repository’s “env access is unsafe” guidance.

As per coding guidelines.

AI prompt for follow-up:
Add a field env_lock: Option<test_support::env_lock::EnvLock> to CliWorld, acquire it when first mutating env (e.g., in stdlib_workspace or home_points_to_stdlib_root), and drop it in CliWorld::drop after restoring vars.
📜 Review details

Configuration used: CodeRabbit UI

Review profile: ASSERTIVE

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between d3aec13 and 2f69f0f.

⛔ Files ignored due to path filters (1)
  • Cargo.lock is excluded by !**/*.lock
📒 Files selected for processing (10)
  • Cargo.toml (1 hunks)
  • src/stdlib/command.rs (1 hunks)
  • src/stdlib/network.rs (1 hunks)
  • test_support/src/command_helper.rs (1 hunks)
  • test_support/src/lib.rs (1 hunks)
  • tests/cucumber.rs (3 hunks)
  • tests/features/stdlib.feature (1 hunks)
  • tests/std_filter_tests/command_filters.rs (1 hunks)
  • tests/std_filter_tests/network_functions.rs (1 hunks)
  • tests/steps/stdlib_steps.rs (6 hunks)
🧰 Additional context used
📓 Path-based instructions (3)
**/*.rs

📄 CodeRabbit inference engine (AGENTS.md)

**/*.rs: Keep functions small with single responsibility; apply command-query separation
Function documentation must include clear examples; test documentation should omit redundant examples
No single Rust code file may exceed 400 lines; split long switches/dispatch tables; move large test data to external files
Disallow Clippy warnings; code must be clean under clippy
Fix warnings emitted during tests in code, do not silence them
Extract helper functions when a function is too long; maintain separation of concerns and CQRS
Group many related parameters into well-named structs
Consider using Arc for large error values to reduce data returned
Every module must begin with a module-level //! comment describing purpose and utility
Document public APIs with Rustdoc /// comments so cargo doc can generate docs
Prefer immutable data; avoid unnecessary mut bindings
Use Result for error handling instead of panicking where feasible
Avoid unsafe code unless absolutely necessary; document any usage clearly
Place function attributes after doc comments
Do not use return in single-line functions
Use predicate functions for conditionals with more than two branches
Do not silence lints except as a last resort
When suppressing lints, scope narrowly and include a clear reason
Prefer #[expect(...)] over #[allow(...)] for lint handling
When functions are unused for certain features, gate with #[cfg]/#[cfg_attr]
Prefer .expect() over .unwrap()
Use concat!() for long string literals instead of escaping newlines with backslashes
Prefer single-line function bodies where appropriate (e.g., pub fn new(id: u64) -> Self { Self(id) })
Use cap-std for filesystem operations; avoid std::fs directly
Use camino for path handling; avoid std::path::PathBuf directly
Prefer semantic error enums deriving std::error::Error via thiserror for inspectable conditions
Use opaque eyre::Report only at the app boundary; do not expose in public APIs
Never export opaque error types from libraries; convert to domain...

Files:

  • test_support/src/lib.rs
  • src/stdlib/command.rs
  • tests/cucumber.rs
  • src/stdlib/network.rs
  • tests/steps/stdlib_steps.rs
  • tests/std_filter_tests/command_filters.rs
  • test_support/src/command_helper.rs
  • tests/std_filter_tests/network_functions.rs

⚙️ CodeRabbit configuration file

**/*.rs: * Seek to keep the cyclomatic complexity of functions no more than 12.

  • Adhere to single responsibility and CQRS

  • Place function attributes after doc comments.

  • Do not use return in single-line functions.

  • Move conditionals with >2 branches into a predicate function.

  • Avoid unsafe unless absolutely necessary.

  • Every module must begin with a //! doc comment that explains the module's purpose and utility.

  • Comments and docs must follow en-GB-oxendict (-ize / -yse / -our) spelling and grammar

  • Lints must not be silenced except as a last resort.

    • #[allow] is forbidden.
    • Only narrowly scoped #[expect(lint, reason = "...")] is allowed.
    • No lint groups, no blanket or file-wide suppression.
    • Include FIXME: with link if a fix is expected.
  • Where code is only used by specific features, it must be conditionally compiled or a conditional expectation for unused_code applied.

  • Use rstest fixtures for shared setup and to avoid repetition between tests.

  • Replace duplicated tests with #[rstest(...)] parameterised cases.

  • Prefer mockall for mocks/stubs.

  • Prefer .expect() over .unwrap()

  • Ensure that any API or behavioural changes are reflected in the documentation in docs/

  • Ensure that any completed roadmap steps are recorded in the appropriate roadmap in docs/

  • Files must not exceed 400 lines in length

    • Large modules must be decomposed
    • Long match statements or dispatch tables should be decomposed by domain and collocated with targets
    • Large blocks of inline data (e.g., test fixtures, constants or templates) must be moved to external files and inlined at compile-time or loaded at run-time.
  • Environment access (env::set_var and env::remove_var) are always unsafe in Rust 2024 and MUST be marked as such

    • For testing of functionality depending upon environment variables, dependency injection and the mockable crate are the preferred option.
    • If mockable cannot be used, env mutations in...

Files:

  • test_support/src/lib.rs
  • src/stdlib/command.rs
  • tests/cucumber.rs
  • src/stdlib/network.rs
  • tests/steps/stdlib_steps.rs
  • tests/std_filter_tests/command_filters.rs
  • test_support/src/command_helper.rs
  • tests/std_filter_tests/network_functions.rs
Cargo.toml

📄 CodeRabbit inference engine (AGENTS.md)

Cargo.toml: Use explicit version ranges in Cargo.toml and keep dependencies up to date
All dependencies must use SemVer caret requirements (e.g., "1.2.3")
Forbid wildcard (*) and open-ended (>=) version specifiers; use ~ only with documented, specific reasons

Files:

  • Cargo.toml
tests/**/*.rs

📄 CodeRabbit inference engine (AGENTS.md)

tests/**/*.rs: Write unit and behavioural tests for new functionality; run before and after changes
Use rstest fixtures for shared setup
Replace duplicated tests with #[rstest(...)] parameterised cases
Prefer mockall for mocks/stubs
Mock nondeterministic deps via dependency injection using mockable traits (e.g., Env, Clock); follow docs/reliable-testing-in-rust-via-dependency-injection.md

Files:

  • tests/cucumber.rs
  • tests/steps/stdlib_steps.rs
  • tests/std_filter_tests/command_filters.rs
  • tests/std_filter_tests/network_functions.rs
🧬 Code graph analysis (4)
src/stdlib/command.rs (1)
src/stdlib/mod.rs (1)
  • register (69-79)
tests/steps/stdlib_steps.rs (3)
test_support/src/command_helper.rs (1)
  • compile_uppercase_helper (15-31)
src/stdlib/command.rs (2)
  • register (49-67)
  • from (329-331)
src/stdlib/mod.rs (2)
  • register (69-79)
  • is_impure (39-41)
tests/std_filter_tests/command_filters.rs (4)
test_support/src/command_helper.rs (1)
  • compile_uppercase_helper (15-31)
tests/std_filter_tests/support.rs (1)
  • stdlib_env_with_state (22-26)
src/stdlib/mod.rs (1)
  • is_impure (39-41)
test_support/src/env_lock.rs (1)
  • acquire (17-21)
tests/std_filter_tests/network_functions.rs (2)
tests/std_filter_tests/support.rs (1)
  • stdlib_env_with_state (22-26)
src/stdlib/mod.rs (1)
  • is_impure (39-41)
⏰ 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: release / build-windows (aarch64-pc-windows-msvc, arm64, arm64)
  • GitHub Check: release / build-macos (x86_64-apple-darwin, macos-13, macos-x86_64, x86_64)
  • GitHub Check: release / build-windows (x86_64-pc-windows-msvc, amd64, x64)
  • GitHub Check: release / build-linux (x86_64-unknown-linux-gnu, linux-amd64, amd64)
  • GitHub Check: build-test (ubuntu-latest, stable)
🔇 Additional comments (21)
Cargo.toml (1)

47-48: LGTM! Dependencies follow version guidelines.

The ureq and wait-timeout dependencies use appropriate SemVer caret version specifiers as required by the coding guidelines.

src/stdlib/network.rs (9)

1-5: LGTM! Module documentation follows guidelines.

The module begins with a clear //! comment explaining its purpose and utility, as required by the coding guidelines.


24-28: LGTM! Function registration correctly threads impurity tracking.

The fetch function is properly registered with the impure flag captured in the closure.


30-51: LGTM! Fetch logic correctly handles caching and impurity.

The function properly marks templates impure for both cache hits (line 39) and remote fetches, addressing previous review feedback.


123-141: LGTM! Cache reading logic is robust.

The function correctly handles missing files by returning None and properly reports other I/O errors.


143-151: LGTM! Cache writing correctly marks impurity.

The function appropriately marks the template impure before performing filesystem I/O.


153-156: LGTM! Cache key generation is secure.

Using SHA-256 provides strong collision resistance for URL-based cache keys.


158-165: LGTM! Hex encoding is efficient.

The function pre-allocates the string with the correct capacity and safely discards the write! Result since writing to a String is infallible.


167-172: LGTM! Value conversion avoids unnecessary allocations.

The function efficiently consumes the input buffer and reuses it when UTF-8 decoding fails, addressing previous review feedback.


174-179: LGTM! Centralised error handling improves consistency.

The helper function provides uniform error formatting across I/O operations.

test_support/src/lib.rs (1)

14-14: LGTM! Module declaration follows conventions.

The addition of the command_helper module is straightforward and maintains file length below the 400-line guideline.

tests/features/stdlib.feature (4)

86-89: LGTM! Grep scenario is portable.

The scenario uses inline text rather than external commands, ensuring cross-platform compatibility.


91-95: LGTM! Fetch scenario correctly tests remote content retrieval.

The scenario appropriately uses an HTTP server fixture and validates both content and impurity tracking.


97-100: LGTM! Fetch error scenario validates error handling.

The scenario correctly tests error reporting for network failures and confirms impurity tracking occurs even on errors.


75-80: ```bash
#!/bin/bash
sed -n '1,200p' test_support/src/command_helper.rs


</blockquote></details>
<details>
<summary>tests/std_filter_tests/network_functions.rs (3)</summary><blockquote>

`34-51`: **LGTM! Test correctly validates fetch and impurity tracking.**

The test properly resets impure state, verifies content retrieval, confirms impurity marking, and cleans up the server thread.

---

`53-87`: **LGTM! Test thoroughly validates caching behaviour.**

The test correctly verifies both cache population and cache hits, confirming impurity tracking in both scenarios.

---

`13-32`: **Remove thread cleanup warning**: tests already call `handle.join()` at lines 50 and 76, ensuring server threads terminate. 

> Likely an incorrect or invalid review comment.

</blockquote></details>
<details>
<summary>tests/cucumber.rs (1)</summary><blockquote>

`69-79`: **Avoid teardown hangs by using a timed connect**

Use connect_timeout to ensure teardown never blocks if the URL is malformed or unreachable.

[ suggest_recommended_refactor ]
Apply:

```diff
-            {
-                let _ = TcpStream::connect(host);
-            }
+            {
+                if let Ok(sock) = host.parse::<std::net::SocketAddr>() {
+                    let _ = std::net::TcpStream::connect_timeout(
+                        &sock,
+                        std::time::Duration::from_millis(100),
+                    );
+                } else {
+                    // Fallback if parsing fails
+                    let _ = TcpStream::connect(host);
+                }
+            }
tests/std_filter_tests/command_filters.rs (1)

60-77: Good coverage for timeout behaviour

The timeout test exercises the default command timeout and error mapping. LGTM.

tests/steps/stdlib_steps.rs (1)

139-143: Capture and expose stdlib impurity state

Registering stdlib, resetting impurity, and storing the state in world is correct and enables the new impurity assertions. LGTM.

codescene-delta-analysis[bot]

This comment was marked as outdated.

codescene-delta-analysis[bot]

This comment was marked as outdated.

Copy link
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)
tests/steps/stdlib_steps.rs (1)

186-202: HTTP server cleanup implemented per previous review.

The step now pings the old server URL with TcpStream::connect before joining, which should unblock the accept() call and prevent hangs. This matches the pattern recommended in the previous review.

📜 Review details

Configuration used: CodeRabbit UI

Review profile: ASSERTIVE

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between 2f69f0f and b58c85b.

📒 Files selected for processing (7)
  • src/stdlib/command.rs (1 hunks)
  • src/stdlib/network.rs (1 hunks)
  • test_support/src/command_helper.rs (1 hunks)
  • tests/features/stdlib.feature (1 hunks)
  • tests/std_filter_tests/command_filters.rs (1 hunks)
  • tests/std_filter_tests/network_functions.rs (1 hunks)
  • tests/steps/stdlib_steps.rs (6 hunks)
🧰 Additional context used
📓 Path-based instructions (2)
**/*.rs

📄 CodeRabbit inference engine (AGENTS.md)

**/*.rs: Keep functions small with single responsibility; apply command-query separation
Function documentation must include clear examples; test documentation should omit redundant examples
No single Rust code file may exceed 400 lines; split long switches/dispatch tables; move large test data to external files
Disallow Clippy warnings; code must be clean under clippy
Fix warnings emitted during tests in code, do not silence them
Extract helper functions when a function is too long; maintain separation of concerns and CQRS
Group many related parameters into well-named structs
Consider using Arc for large error values to reduce data returned
Every module must begin with a module-level //! comment describing purpose and utility
Document public APIs with Rustdoc /// comments so cargo doc can generate docs
Prefer immutable data; avoid unnecessary mut bindings
Use Result for error handling instead of panicking where feasible
Avoid unsafe code unless absolutely necessary; document any usage clearly
Place function attributes after doc comments
Do not use return in single-line functions
Use predicate functions for conditionals with more than two branches
Do not silence lints except as a last resort
When suppressing lints, scope narrowly and include a clear reason
Prefer #[expect(...)] over #[allow(...)] for lint handling
When functions are unused for certain features, gate with #[cfg]/#[cfg_attr]
Prefer .expect() over .unwrap()
Use concat!() for long string literals instead of escaping newlines with backslashes
Prefer single-line function bodies where appropriate (e.g., pub fn new(id: u64) -> Self { Self(id) })
Use cap-std for filesystem operations; avoid std::fs directly
Use camino for path handling; avoid std::path::PathBuf directly
Prefer semantic error enums deriving std::error::Error via thiserror for inspectable conditions
Use opaque eyre::Report only at the app boundary; do not expose in public APIs
Never export opaque error types from libraries; convert to domain...

Files:

  • test_support/src/command_helper.rs
  • src/stdlib/network.rs
  • tests/steps/stdlib_steps.rs
  • tests/std_filter_tests/command_filters.rs
  • tests/std_filter_tests/network_functions.rs
  • src/stdlib/command.rs

⚙️ CodeRabbit configuration file

**/*.rs: * Seek to keep the cyclomatic complexity of functions no more than 12.

  • Adhere to single responsibility and CQRS

  • Place function attributes after doc comments.

  • Do not use return in single-line functions.

  • Move conditionals with >2 branches into a predicate function.

  • Avoid unsafe unless absolutely necessary.

  • Every module must begin with a //! doc comment that explains the module's purpose and utility.

  • Comments and docs must follow en-GB-oxendict (-ize / -yse / -our) spelling and grammar

  • Lints must not be silenced except as a last resort.

    • #[allow] is forbidden.
    • Only narrowly scoped #[expect(lint, reason = "...")] is allowed.
    • No lint groups, no blanket or file-wide suppression.
    • Include FIXME: with link if a fix is expected.
  • Where code is only used by specific features, it must be conditionally compiled or a conditional expectation for unused_code applied.

  • Use rstest fixtures for shared setup and to avoid repetition between tests.

  • Replace duplicated tests with #[rstest(...)] parameterised cases.

  • Prefer mockall for mocks/stubs.

  • Prefer .expect() over .unwrap()

  • Ensure that any API or behavioural changes are reflected in the documentation in docs/

  • Ensure that any completed roadmap steps are recorded in the appropriate roadmap in docs/

  • Files must not exceed 400 lines in length

    • Large modules must be decomposed
    • Long match statements or dispatch tables should be decomposed by domain and collocated with targets
    • Large blocks of inline data (e.g., test fixtures, constants or templates) must be moved to external files and inlined at compile-time or loaded at run-time.
  • Environment access (env::set_var and env::remove_var) are always unsafe in Rust 2024 and MUST be marked as such

    • For testing of functionality depending upon environment variables, dependency injection and the mockable crate are the preferred option.
    • If mockable cannot be used, env mutations in...

Files:

  • test_support/src/command_helper.rs
  • src/stdlib/network.rs
  • tests/steps/stdlib_steps.rs
  • tests/std_filter_tests/command_filters.rs
  • tests/std_filter_tests/network_functions.rs
  • src/stdlib/command.rs
tests/**/*.rs

📄 CodeRabbit inference engine (AGENTS.md)

tests/**/*.rs: Write unit and behavioural tests for new functionality; run before and after changes
Use rstest fixtures for shared setup
Replace duplicated tests with #[rstest(...)] parameterised cases
Prefer mockall for mocks/stubs
Mock nondeterministic deps via dependency injection using mockable traits (e.g., Env, Clock); follow docs/reliable-testing-in-rust-via-dependency-injection.md

Files:

  • tests/steps/stdlib_steps.rs
  • tests/std_filter_tests/command_filters.rs
  • tests/std_filter_tests/network_functions.rs
🧬 Code graph analysis (4)
tests/steps/stdlib_steps.rs (3)
test_support/src/command_helper.rs (2)
  • compile_failure_helper (68-70)
  • compile_uppercase_helper (46-48)
src/stdlib/command.rs (2)
  • register (49-67)
  • from (323-325)
src/stdlib/mod.rs (2)
  • register (69-79)
  • is_impure (39-41)
tests/std_filter_tests/command_filters.rs (4)
test_support/src/command_helper.rs (3)
  • compile_failure_helper (68-70)
  • compile_uppercase_helper (46-48)
  • compile_rust_helper (98-114)
tests/std_filter_tests/support.rs (1)
  • stdlib_env_with_state (22-26)
src/stdlib/mod.rs (1)
  • is_impure (39-41)
test_support/src/env_lock.rs (1)
  • acquire (17-21)
tests/std_filter_tests/network_functions.rs (2)
tests/std_filter_tests/support.rs (1)
  • stdlib_env_with_state (22-26)
src/stdlib/mod.rs (1)
  • is_impure (39-41)
src/stdlib/command.rs (1)
src/stdlib/mod.rs (1)
  • register (69-79)
🔍 Remote MCP

Additional context summary relevant to reviewing PR #187:

  1. Dependency choices:
    • ureq v2.10.5: default blocking HTTP client with connection pooling and configurable timeouts (implicit) [Ref::].
  2. Shell-quote crate ensures safe POSIX/Windows quoting for shell and grep filters, preventing injection [Deepwiki::Overview].
  3. Stdlib registration in src/stdlib/mod.rs now returns StdlibState to track impurity across new network/command helpers [ai_summary].
  4. wait-timeout v0.2 dependency provides cross‐platform process timeout support for run_command [ai_summary].
  5. The new stdlib modules (network.rs, command.rs) follow existing patterns for function registration and error propagation via thiserror/anyhow [Deepwiki::Template System].
⏰ 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). (8)
  • GitHub Check: release / build-macos (aarch64-apple-darwin, macos-14, macos-arm64, arm64)
  • GitHub Check: release / build-macos (x86_64-apple-darwin, macos-13, macos-x86_64, x86_64)
  • GitHub Check: release / build-windows (aarch64-pc-windows-msvc, arm64, arm64)
  • GitHub Check: release / build-windows (x86_64-pc-windows-msvc, amd64, x64)
  • GitHub Check: release / build-linux (x86_64-unknown-linux-gnu, linux-amd64, amd64)
  • GitHub Check: release / build-linux (aarch64-unknown-linux-gnu, linux-arm64, arm64)
  • GitHub Check: Sourcery review
  • GitHub Check: build-test (ubuntu-latest, stable)
🔇 Additional comments (25)
test_support/src/command_helper.rs (1)

1-114: LGTM! Module documentation and examples are comprehensive.

The test-support helpers are well-structured with clear module-level documentation and complete Rustdoc for each public function. The use of cap-std for filesystem operations and camino for path handling aligns with coding guidelines. The expect() calls are appropriate for test-support code.

tests/features/stdlib.feature (1)

75-101: LGTM! Cross-platform compatibility achieved with compiled helpers.

The new scenarios correctly test command and network filters whilst verifying impurity tracking. Using compiled Rust helpers (uppercase stdlib command helper, failing stdlib command helper) instead of shell commands like tr and false ensures the tests run reliably on Windows.

tests/std_filter_tests/command_filters.rs (1)

1-238: LGTM! Comprehensive test coverage with proper platform handling.

The test suite thoroughly exercises command filters with appropriate separation of Unix/Windows behaviour. Key strengths:

  • Uses shared compile_uppercase_helper and compile_failure_helper from test_support
  • Verifies impurity marking in all code paths (success, failure, undefined input)
  • Windows-specific tests properly use EnvLock fixture to guard PATH mutations (lines 190, 217)
  • Platform-specific tests gated with #[cfg(unix)] and #[cfg(windows)]
tests/std_filter_tests/network_functions.rs (1)

1-107: LGTM! Network tests verify impurity on all code paths.

The test suite correctly exercises fetch functionality including caching and error handling. The start_server helper (lines 13-32) provides a simple but effective local HTTP server for testing. Impurity is properly verified on success (line 47), cached responses (lines 72, 84), and errors (lines 103-106).

src/stdlib/network.rs (1)

1-189: LGTM! Network module correctly implements fetch with impurity tracking.

The implementation properly addresses all past review feedback:

  • Configures HTTP timeouts via AgentBuilder (lines 56-61) to prevent indefinite hangs
  • Marks templates impure on remote fetch (line 55), cache hits (line 40), and cache writes (line 154)
  • Handles absolute paths on Windows by parsing components (lines 88-123)
  • Uses proper error handling without expect() (lines 107-112)
  • Optimises to_value to avoid cloning by consuming the Vec (lines 178-181)
tests/steps/stdlib_steps.rs (6)

12-20: Previous server cleanup concern appears addressed.

The imports include TcpStream as recommended in the previous review for unblocking the HTTP server before joining.


141-157: LGTM! Correct impurity state management.

The function correctly captures the stdlib state, resets impurity before rendering, and stores a clone for subsequent assertions. This aligns with the new stdlib API that returns StdlibState from register().


170-184: LGTM! Command helper setup follows consistent pattern.

Both steps correctly compile the helper binaries and store the quoted command path. The expect calls are appropriate for test setup—panicking on failure is acceptable in test infrastructure.


268-286: LGTM! Rendering steps correctly propagate test fixtures.

Both steps extract the stored values from world and inject them into the template context with appropriate keys. The expect calls are suitable for test steps, ensuring fixtures are initialised.


370-386: LGTM! Impurity assertions correctly verify stdlib state.

Both steps properly access the stored StdlibState and assert the impurity flag. The error messages clearly communicate expectations.


100-119: Remove single-request server critique
The tests spawn a fresh HTTP server per fetch and never reuse the same URL across multiple requests, so the single-request design is intentional and sufficient.

Likely an incorrect or invalid review comment.

src/stdlib/command.rs (14)

1-13: LGTM! Module documentation includes security warnings.

The module doc clearly describes purpose and includes a security section warning about command injection risks, addressing the previous review concern.


49-67: LGTM! Filter registration correctly tracks impurity.

Both filters mark the stdlib state as impure before execution, using Ordering::Relaxed which is appropriate for this use case. The pattern aligns with the network module.


69-81: LGTM! Shell execution follows single responsibility.

The function validates input, executes the command, and converts output, with clear error propagation at each step.


83-110: LGTM! Grep execution handles platform differences correctly.

Windows invokes grep directly via run_program, avoiding shell quoting issues, whilst Unix uses shell invocation with proper POSIX quoting. This addresses the previous review concern about cmd.exe quoting.


112-136: LGTM! Flag collection handles various value types.

The function correctly handles different value kinds and validates that all flags are strings. The error messages are specific to grep, which is appropriate since this is an internal helper for execute_grep.


139-157: LGTM! Platform-appropriate quoting prevents injection.

Windows uses shell_quote::windows::quote and Unix uses POSIX quoting, ensuring arguments are safely escaped for each platform's shell. This addresses the previous review concern.


159-172: LGTM! Generic error message addresses previous review.

The error message "input value is undefined" is now generic and suitable for both shell and grep filters.


174-183: LGTM! Shell invocation uses platform-specific constants.

The function correctly configures the shell command with piped I/O and delegates to run_child for execution.


185-194: LGTM! Direct programme invocation avoids Windows shell issues.

The Windows-specific run_program function invokes the programme directly without a shell, sidestepping cmd.exe quoting complexities.


196-243: LGTM! Robust process execution with timeout and concurrent I/O.

The function correctly:

  • Handles broken pipes when the child closes stdin early
  • Reads stdout/stderr concurrently to prevent deadlock
  • Enforces a timeout with cleanup
  • Returns detailed failure information including stderr

The complexity is justified given the IPC requirements. Past review concerns about timeout and broken pipe handling have been addressed.


245-250: LGTM! UTF-8 conversion with byte fallback.

The function prefers UTF-8 strings but gracefully handles binary output, which is appropriate for command output that may not be text.


252-305: LGTM! Error messages use consistent "command" terminology.

The function maps all failure modes to user-facing errors with clear context. The error messages now consistently use "command" rather than "shell command", addressing the previous review concern. The broken pipe error includes the explanation "(command closed input early)", which is helpful for debugging.


366-373: LGTM! Stderr formatting helper is clean and concise.

The function correctly converts stderr to UTF-8 (lossily), trims whitespace, and appends to the error message with appropriate formatting.


328-340: Robustify kill error handling in wait_for_exit
Handle the race condition where the child may exit just before kill: ensure only InvalidInput is suppressed and any other Err from kill propagates as CommandFailure::Io.

Copy link
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

Caution

Some comments are outside the diff and can’t be posted inline due to platform limitations.

⚠️ Outside diff range comments (1)
tests/steps/stdlib_steps.rs (1)

1-4: Update the module doc to reflect network and command steps

Document that this module also hosts network (HTTP server, fetch context) and command-helper steps, not only path/file filters.

♻️ Duplicate comments (3)
src/stdlib/network.rs (1)

88-123: Simplify absolute cache‑dir handling

Remove manual root/prefix splitting and use fs_utf8::create_dir_all with ambient authority, then reopen the directory. This reduces platform corner cases.

Apply this diff here:

-    if utf_path.is_absolute() {
-        let mut root = String::new();
-        for component in utf_path.components() {
-            match component {
-                Utf8Component::Prefix(prefix) => root.push_str(prefix.as_str()),
-                Utf8Component::RootDir => {
-                    root.push('/');
-                    break;
-                }
-                _ => break,
-            }
-        }
-        if root.is_empty() {
-            root.push('/');
-        }
-        let root_path = Utf8PathBuf::from(root);
-
-        let root_dir = Dir::open_ambient_dir(&root_path, ambient_authority())
-            .map_err(|err| io_error("open root cache dir", &root_path, &err))?;
-        let rel = utf_path.strip_prefix(&root_path).map_err(|_| {
-            Error::new(
-                ErrorKind::InvalidOperation,
-                format!("failed to compute relative path for '{utf_path}'"),
-            )
-        })?;
-        if rel.as_str().is_empty() {
-            return Ok(root_dir);
-        }
-
-        root_dir
-            .create_dir_all(rel)
-            .map_err(|err| io_error("create cache dir", utf_path, &err))?;
-        return root_dir
-            .open_dir(rel)
-            .map_err(|err| io_error("open cache dir", utf_path, &err));
-    }
+    if utf_path.is_absolute() {
+        cap_std::fs_utf8::create_dir_all(utf_path, ambient_authority())
+            .map_err(|err| io_error("create cache dir", utf_path, &err))?;
+        return Dir::open_ambient_dir(utf_path, ambient_authority())
+            .map_err(|err| io_error("open cache dir", utf_path, &err));
+    }

Add the import:

- use cap_std::{ambient_authority, fs_utf8::Dir};
+ use cap_std::{ambient_authority, fs_utf8::Dir};
+ // uses fully-qualified cap_std::fs_utf8::create_dir_all above; alternatively import it:
+ // use cap_std::fs_utf8::{Dir, create_dir_all};
test_support/src/command_helper.rs (1)

1-2: Good module-level documentation; resolves prior review ask

Keep this at the top of every module as per guidelines.

tests/steps/stdlib_steps.rs (1)

186-202: Unblock previous HTTP server before joining — well done

Ping the old URL to unblock accept() before join. This resolves the previously flagged hang risk.

📜 Review details

Configuration used: CodeRabbit UI

Review profile: ASSERTIVE

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between 2f69f0f and b58c85b.

📒 Files selected for processing (7)
  • src/stdlib/command.rs (1 hunks)
  • src/stdlib/network.rs (1 hunks)
  • test_support/src/command_helper.rs (1 hunks)
  • tests/features/stdlib.feature (1 hunks)
  • tests/std_filter_tests/command_filters.rs (1 hunks)
  • tests/std_filter_tests/network_functions.rs (1 hunks)
  • tests/steps/stdlib_steps.rs (6 hunks)
🧰 Additional context used
📓 Path-based instructions (2)
**/*.rs

📄 CodeRabbit inference engine (AGENTS.md)

**/*.rs: Keep functions small with single responsibility; apply command-query separation
Function documentation must include clear examples; test documentation should omit redundant examples
No single Rust code file may exceed 400 lines; split long switches/dispatch tables; move large test data to external files
Disallow Clippy warnings; code must be clean under clippy
Fix warnings emitted during tests in code, do not silence them
Extract helper functions when a function is too long; maintain separation of concerns and CQRS
Group many related parameters into well-named structs
Consider using Arc for large error values to reduce data returned
Every module must begin with a module-level //! comment describing purpose and utility
Document public APIs with Rustdoc /// comments so cargo doc can generate docs
Prefer immutable data; avoid unnecessary mut bindings
Use Result for error handling instead of panicking where feasible
Avoid unsafe code unless absolutely necessary; document any usage clearly
Place function attributes after doc comments
Do not use return in single-line functions
Use predicate functions for conditionals with more than two branches
Do not silence lints except as a last resort
When suppressing lints, scope narrowly and include a clear reason
Prefer #[expect(...)] over #[allow(...)] for lint handling
When functions are unused for certain features, gate with #[cfg]/#[cfg_attr]
Prefer .expect() over .unwrap()
Use concat!() for long string literals instead of escaping newlines with backslashes
Prefer single-line function bodies where appropriate (e.g., pub fn new(id: u64) -> Self { Self(id) })
Use cap-std for filesystem operations; avoid std::fs directly
Use camino for path handling; avoid std::path::PathBuf directly
Prefer semantic error enums deriving std::error::Error via thiserror for inspectable conditions
Use opaque eyre::Report only at the app boundary; do not expose in public APIs
Never export opaque error types from libraries; convert to domain...

Files:

  • src/stdlib/command.rs
  • tests/std_filter_tests/network_functions.rs
  • tests/std_filter_tests/command_filters.rs
  • test_support/src/command_helper.rs
  • src/stdlib/network.rs
  • tests/steps/stdlib_steps.rs

⚙️ CodeRabbit configuration file

**/*.rs: * Seek to keep the cyclomatic complexity of functions no more than 12.

  • Adhere to single responsibility and CQRS

  • Place function attributes after doc comments.

  • Do not use return in single-line functions.

  • Move conditionals with >2 branches into a predicate function.

  • Avoid unsafe unless absolutely necessary.

  • Every module must begin with a //! doc comment that explains the module's purpose and utility.

  • Comments and docs must follow en-GB-oxendict (-ize / -yse / -our) spelling and grammar

  • Lints must not be silenced except as a last resort.

    • #[allow] is forbidden.
    • Only narrowly scoped #[expect(lint, reason = "...")] is allowed.
    • No lint groups, no blanket or file-wide suppression.
    • Include FIXME: with link if a fix is expected.
  • Where code is only used by specific features, it must be conditionally compiled or a conditional expectation for unused_code applied.

  • Use rstest fixtures for shared setup and to avoid repetition between tests.

  • Replace duplicated tests with #[rstest(...)] parameterised cases.

  • Prefer mockall for mocks/stubs.

  • Prefer .expect() over .unwrap()

  • Ensure that any API or behavioural changes are reflected in the documentation in docs/

  • Ensure that any completed roadmap steps are recorded in the appropriate roadmap in docs/

  • Files must not exceed 400 lines in length

    • Large modules must be decomposed
    • Long match statements or dispatch tables should be decomposed by domain and collocated with targets
    • Large blocks of inline data (e.g., test fixtures, constants or templates) must be moved to external files and inlined at compile-time or loaded at run-time.
  • Environment access (env::set_var and env::remove_var) are always unsafe in Rust 2024 and MUST be marked as such

    • For testing of functionality depending upon environment variables, dependency injection and the mockable crate are the preferred option.
    • If mockable cannot be used, env mutations in...

Files:

  • src/stdlib/command.rs
  • tests/std_filter_tests/network_functions.rs
  • tests/std_filter_tests/command_filters.rs
  • test_support/src/command_helper.rs
  • src/stdlib/network.rs
  • tests/steps/stdlib_steps.rs
tests/**/*.rs

📄 CodeRabbit inference engine (AGENTS.md)

tests/**/*.rs: Write unit and behavioural tests for new functionality; run before and after changes
Use rstest fixtures for shared setup
Replace duplicated tests with #[rstest(...)] parameterised cases
Prefer mockall for mocks/stubs
Mock nondeterministic deps via dependency injection using mockable traits (e.g., Env, Clock); follow docs/reliable-testing-in-rust-via-dependency-injection.md

Files:

  • tests/std_filter_tests/network_functions.rs
  • tests/std_filter_tests/command_filters.rs
  • tests/steps/stdlib_steps.rs
🧬 Code graph analysis (4)
src/stdlib/command.rs (1)
src/stdlib/mod.rs (1)
  • register (69-79)
tests/std_filter_tests/network_functions.rs (2)
tests/std_filter_tests/support.rs (1)
  • stdlib_env_with_state (22-26)
src/stdlib/mod.rs (1)
  • is_impure (39-41)
tests/std_filter_tests/command_filters.rs (4)
test_support/src/command_helper.rs (3)
  • compile_failure_helper (68-70)
  • compile_uppercase_helper (46-48)
  • compile_rust_helper (98-114)
tests/std_filter_tests/support.rs (1)
  • stdlib_env_with_state (22-26)
src/stdlib/mod.rs (1)
  • is_impure (39-41)
test_support/src/env_lock.rs (1)
  • acquire (17-21)
tests/steps/stdlib_steps.rs (3)
test_support/src/command_helper.rs (2)
  • compile_failure_helper (68-70)
  • compile_uppercase_helper (46-48)
src/stdlib/command.rs (1)
  • register (49-67)
src/stdlib/mod.rs (2)
  • register (69-79)
  • is_impure (39-41)
⏰ 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). (8)
  • GitHub Check: release / build-macos (aarch64-apple-darwin, macos-14, macos-arm64, arm64)
  • GitHub Check: release / build-macos (x86_64-apple-darwin, macos-13, macos-x86_64, x86_64)
  • GitHub Check: release / build-windows (aarch64-pc-windows-msvc, arm64, arm64)
  • GitHub Check: release / build-windows (x86_64-pc-windows-msvc, amd64, x64)
  • GitHub Check: release / build-linux (x86_64-unknown-linux-gnu, linux-amd64, amd64)
  • GitHub Check: release / build-linux (aarch64-unknown-linux-gnu, linux-arm64, arm64)
  • GitHub Check: Sourcery review
  • GitHub Check: build-test (ubuntu-latest, stable)
🔇 Additional comments (10)
src/stdlib/command.rs (4)

148-157: Windows‑safe quoting is correct

Quote arguments with shell_quote::windows::quote and build the command string safely. This fixes cmd.exe quoting pitfalls.


101-108: Bypass the shell for grep on Windows

Invoke grep directly with Command on Windows. This avoids cmd.exe mis‑parsing and ensures arguments are passed verbatim.

Also applies to: 185-194


252-304: Consistent, actionable error messages

Normalise phrasing to “command …” and include stderr/exit metadata. This improves diagnosability without leaking internals.


159-165: Generic undefined‑input error

Return a shell‑agnostic error for undefined values. This keeps to_bytes reusable across filters.

src/stdlib/network.rs (1)

54-61: Good: explicit HTTP timeouts

Configure connect/read/write/global timeouts on ureq Agent to prevent hangs. Robust default values.

tests/std_filter_tests/network_functions.rs (1)

34-51: Network tests are robust and precise

Validate fetch behaviour, cache hits, and error reporting with clear impurity assertions. Solid coverage.

Also applies to: 53-87, 89-107

tests/std_filter_tests/command_filters.rs (1)

20-39: Command/grep tests cover behaviour and portability well

Exercise success, failure, undefined input, Windows quoting, PATH stubs, and timeouts with clear assertions and guards.

Also applies to: 41-66, 68-85, 87-97, 99-113, 115-133, 135-153, 155-159, 161-176, 188-213, 215-238

tests/features/stdlib.feature (1)

87-91: Ignore grep portability suggestion
The grep filter runs in-process and does not invoke the system grep, so no stub or @unix tag is needed.

Likely an incorrect or invalid review comment.

test_support/src/command_helper.rs (1)

27-48: Public API docs and implementation read cleanly

  • Provide clear, doctest-friendly examples with no_run.
  • Use cap-std and camino correctly; avoid unwrap; use concat! for sources; return last expression.

LGTM.

Also applies to: 50-70, 72-114

tests/steps/stdlib_steps.rs (1)

224-239: Merge as is—environment mutations are serialised
test_support::env::set_var acquires EnvLock backed by a global Mutex, satisfying the project’s guard requirement.

codescene-delta-analysis[bot]

This comment was marked as outdated.

@leynos
Copy link
Owner Author

leynos commented Oct 10, 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:

Complex Conditional

tests/cucumber.rs: Drop.drop

What lead to degradation?

Drop.drop has 1 complex conditionals with 3 branches, threshold = 2

Why does this problem occur?

A complex conditional is an expression inside a branch such as an if-statmeent which consists of multiple, logical operations. Example: if (x.started() && y.running()).Complex conditionals make the code even harder to read, and contribute to the Complex Method code smell. Encapsulate them.

How to fix it?

Apply the DECOMPOSE CONDITIONAL refactoring so that the complex conditional is encapsulated in a separate function with a good name that captures the business rule. Optionally, for simple expressions, introduce a new variable which holds the result of the complex conditional.

Helpful refactoring examples

To get a general understanding of what this code health issue looks like - and how it might be addressed - we have prepared some diffs for illustrative purposes.

SAMPLE

# complex_conditional.js
 function messageReceived(message, timeReceived) {
-   // Ignore all messages which aren't from known customers:
-   if (!message.sender &&
-       customers.getId(message.name) == null) {
+   // Refactoring #1: encapsulate the business rule in a
+   // function. A clear name replaces the need for the comment:
+   if (!knownCustomer(message)) {
      log('spam received -- ignoring');
      return;
    }
 
-  // Provide an auto-reply when outside business hours:
-  if ((timeReceived.getHours() > 17) ||
-      (timeReceived.getHours() < 8)) {
+  // Refactoring #2: encapsulate the business rule.
+  // Again, note how a clear function name replaces the
+  // need for a code comment:
+  if (outsideBusinessHours(timeReceived)) {
     return autoReplyTo(message);
   }
 
   pingAgentFor(message);
+}
+
+function outsideBusinessHours(timeReceived) {
+  // Refactoring #3: replace magic numbers with
+  // symbols that communicate with the code reader:
+  const closingHour = 17;
+  const openingHour = 8;
+
+  const hours = timeReceived.getHours();
+
+  // Refactoring #4: simple conditional rules can
+  // be further clarified by introducing a variable:
+  const afterClosing = hours > closingHour;
+  const beforeOpening = hours < openingHour;
+
+  // Yeah -- look how clear the business rule is now!
+  return afterClosing || beforeOpening;
 }

@leynos
Copy link
Owner Author

leynos commented Oct 10, 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:

Complex Conditional

tests/steps/stdlib_steps.rs: http_server_returning

What lead to degradation?

http_server_returning has 1 complex conditionals with 3 branches, threshold = 2

Why does this problem occur?

A complex conditional is an expression inside a branch such as an if-statmeent which consists of multiple, logical operations. Example: if (x.started() && y.running()).Complex conditionals make the code even harder to read, and contribute to the Complex Method code smell. Encapsulate them.

How to fix it?

Apply the DECOMPOSE CONDITIONAL refactoring so that the complex conditional is encapsulated in a separate function with a good name that captures the business rule. Optionally, for simple expressions, introduce a new variable which holds the result of the complex conditional.

Helpful refactoring examples

To get a general understanding of what this code health issue looks like - and how it might be addressed - we have prepared some diffs for illustrative purposes.

SAMPLE

# complex_conditional.js
 function messageReceived(message, timeReceived) {
-   // Ignore all messages which aren't from known customers:
-   if (!message.sender &&
-       customers.getId(message.name) == null) {
+   // Refactoring #1: encapsulate the business rule in a
+   // function. A clear name replaces the need for the comment:
+   if (!knownCustomer(message)) {
      log('spam received -- ignoring');
      return;
    }
 
-  // Provide an auto-reply when outside business hours:
-  if ((timeReceived.getHours() > 17) ||
-      (timeReceived.getHours() < 8)) {
+  // Refactoring #2: encapsulate the business rule.
+  // Again, note how a clear function name replaces the
+  // need for a code comment:
+  if (outsideBusinessHours(timeReceived)) {
     return autoReplyTo(message);
   }
 
   pingAgentFor(message);
+}
+
+function outsideBusinessHours(timeReceived) {
+  // Refactoring #3: replace magic numbers with
+  // symbols that communicate with the code reader:
+  const closingHour = 17;
+  const openingHour = 8;
+
+  const hours = timeReceived.getHours();
+
+  // Refactoring #4: simple conditional rules can
+  // be further clarified by introducing a variable:
+  const afterClosing = hours > closingHour;
+  const beforeOpening = hours < openingHour;
+
+  // Yeah -- look how clear the business rule is now!
+  return afterClosing || beforeOpening;
 }

@coderabbitai

This comment was marked as resolved.

@coderabbitai

This comment was marked as resolved.

codescene-delta-analysis[bot]

This comment was marked as outdated.

@leynos
Copy link
Owner Author

leynos commented Oct 10, 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:

tests/std_filter_tests/command_filters.rs

Comment on lines +219 to +240

fn env_lock() -> EnvLock {
    EnvLock::acquire()
}

#[cfg(windows)]
const GREP_STUB: &str = concat!(
    "use std::io::{self, Read};\n",
    "fn main() {\n",
    "    let mut args: Vec<String> = std::env::args().skip(1).collect();\n",
    "    let pattern = args.pop().expect(\"pattern\");\n",
    "    let mut input = String::new();\n",
    "    io::stdin().read_to_string(&mut input).expect(\"stdin\");\n",
    "    if pattern == \"^line2\" && input.contains(\"line2\") {\n",
    "        print!(\"line2\\n\");\n",
    "    } else {\n",
    "        eprintln!(\"pattern:{pattern} input:{input}\", pattern = pattern, input = input);\n",
    "        std::process::exit(1);\n",
    "    }\n",
    "}\n",
);

#[cfg(windows)]

❌ New issue: Code Duplication
The module contains 2 functions with similar structure: shell_filter_marks_templates_impure,shell_preserves_cmd_meta_characters

@coderabbitai
Copy link
Contributor

coderabbitai bot commented Oct 10, 2025

Tip

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

🧩 Analysis chain

🏁 Script executed:

#!/bin/bash
# Read the command_filters.rs file to examine the duplicated functions
cat tests/std_filter_tests/command_filters.rs

Length of output: 9485


@leynos: I'll analyse the code duplication and suggest a refactoring.

Analysis

The duplication occurs between the Windows-specific tests shell_preserves_cmd_meta_characters and grep_on_windows_bypasses_shell. Both tests follow an identical pattern:

  1. Acquire environment lock
  2. Create temporary directory and ambient Dir
  3. Compile a custom Rust helper
  4. Optionally modify PATH environment variable
  5. Create environment with stdlib state
  6. Reset impure state
  7. Add and render a template
  8. Assert results and impurity

The existing CommandFixture struct already abstracts this pattern, but the Windows tests duplicate it manually because they need PATH manipulation.

Suggested Fix

Extend CommandFixture to support optional environment variable setup for Windows-specific tests:

struct CommandFixture {
    _temp: tempfile::TempDir,
    env: Environment<'static>,
    state: netsuke::stdlib::StdlibState,
    command: String,
    #[cfg(windows)]
    _env_guards: Vec<test_support::EnvVarGuard>,
}

impl CommandFixture {
    fn new(compiler: impl Fn(&Dir, &Utf8PathBuf, &str) -> Utf8PathBuf, binary: &str) -> Self {
        let temp = tempdir().expect("tempdir");
        let root = Utf8PathBuf::from_path_buf(temp.path().to_path_buf()).expect("utf8");
        let dir = Dir::open_ambient_dir(&root, ambient_authority()).expect("dir");
        let helper = compiler(&dir, &root, binary);
        let command = format!("\"{}\"", helper.as_str());
        let (env, state) = stdlib_env_with_state();
        state.reset_impure();
        Self {
            _temp: temp,
            env,
            state,
            command,
            #[cfg(windows)]
            _env_guards: Vec::new(),
        }
    }

    #[cfg(windows)]
    fn with_path_prefix(mut self) -> Self {
        let root = Utf8PathBuf::from_path_buf(self._temp.path().to_path_buf()).expect("utf8");
        let mut path_value = OsString::from(root.as_str());
        path_value.push(";");
        path_value.push(std::env::var_os("PATH").unwrap_or_default());
        self._env_guards.push(EnvVarGuard::set("PATH", &path_value));
        self
    }

    fn env(&mut self) -> &mut Environment<'static> {
        &mut self.env
    }

    fn command(&self) -> &str {
        &self.command
    }

    fn state(&self) -> &netsuke::stdlib::StdlibState {
        &self.state
    }

    fn root_path(&self) -> Utf8PathBuf {
        Utf8PathBuf::from_path_buf(self._temp.path().to_path_buf()).expect("utf8")
    }
}

Then refactor the Windows tests to use this extended fixture:

#[cfg(windows)]
#[rstest]
fn grep_on_windows_bypasses_shell(_env_lock: EnvLock) {
    let compiler = |dir: &Dir, root: &Utf8PathBuf, name: &str| {
        compile_rust_helper(dir, root, name, GREP_STUB)
    };
    let mut fixture = CommandFixture::new(compiler, "grep").with_path_prefix();
    
    fixture.env().add_template(
        "grep_win",
        r#"{{ 'line1\nline2\n' | grep('^line2') | trim }}"#,
    ).expect("template");
    
    let template = fixture.env().get_template("grep_win").expect("get template");
    let rendered = template.render(context! {}).expect("render");
    
    assert_eq!(rendered, "line2");
    assert!(fixture.state().is_impure(), "grep should mark template impure");
}

#[cfg(windows)]
#[rstest]
fn shell_preserves_cmd_meta_characters(_env_lock: EnvLock) {
    let compiler = |dir: &Dir, root: &Utf8PathBuf, name: &str| {
        compile_rust_helper(dir, root, name, ARGS_STUB)
    };
    let fixture = CommandFixture::new(compiler, "echo_args").with_path_prefix();
    let command = format!("{} \"literal %%^!\"", fixture.command());
    
    fixture.env().add_template("shell_meta", "{{ '' | shell(cmd) }}")
        .expect("template");
    let template = fixture.env().get_template("shell_meta").expect("get template");
    let rendered = template.render(context!(cmd => command)).expect("render shell");
    
    assert_eq!(rendered.trim(), "literal %^!");
    assert!(fixture.state().is_impure(), "shell filter should mark template impure");
}

AI Agent Prompt

Refactor the Windows-specific tests in `tests/std_filter_tests/command_filters.rs` to eliminate code duplication by extending the `CommandFixture` struct:

1. Add a new field to `CommandFixture` (Windows-only):
   ```rust
   #[cfg(windows)]
   _env_guards: Vec<test_support::EnvVarGuard>,
   ```
   Initialise this field to `Vec::new()` in the `new` method.

2. Add a new method `with_path_prefix` to `CommandFixture` (Windows-only):
   ```rust
   #[cfg(windows)]
   fn with_path_prefix(mut self) -> Self {
       let root = Utf8PathBuf::from_path_buf(self._temp.path().to_path_buf()).expect("utf8");
       let mut path_value = OsString::from(root.as_str());
       path_value.push(";");
       path_value.push(std::env::var_os("PATH").unwrap_or_default());
       self._env_guards.push(EnvVarGuard::set("PATH", &path_value));
       self
   }
   ```

3. Add a helper method `root_path` to `CommandFixture`:
   ```rust
   fn root_path(&self) -> Utf8PathBuf {
       Utf8PathBuf::from_path_buf(self._temp.path().to_path_buf()).expect("utf8")
   }
   ```

4. Refactor `grep_on_windows_bypasses_shell` to use `CommandFixture`:
   - Replace manual temp dir setup with `CommandFixture::new` using a closure that calls `compile_rust_helper` with `GREP_STUB`
   - Chain `.with_path_prefix()` to add the PATH modification
   - Use `fixture.env()` and `fixture.state()` throughout
   - Keep the same assertions

5. Refactor `shell_preserves_cmd_meta_characters` similarly:
   - Replace manual setup with `CommandFixture::new` using a closure with `ARGS_STUB`
   - Chain `.with_path_prefix()`
   - Use `fixture.command()` to build the command string
   - Keep the same assertions

6. Add `use std::ffi::OsString;` to the Windows-specific imports at the top if not already present.

This refactoring eliminates ~30 lines of duplicated setup code whilst maintaining the same test behaviour.

Copy link
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: CodeRabbit UI

Review profile: ASSERTIVE

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between ff68165 and 2addbee.

📒 Files selected for processing (7)
  • src/stdlib/command.rs (1 hunks)
  • src/stdlib/network.rs (1 hunks)
  • tests/cucumber.rs (3 hunks)
  • tests/std_filter_tests/command_filters.rs (1 hunks)
  • tests/std_filter_tests/network_functions.rs (1 hunks)
  • tests/steps/mod.rs (1 hunks)
  • tests/steps/stdlib_steps.rs (6 hunks)
🧰 Additional context used
📓 Path-based instructions (2)
**/*.rs

📄 CodeRabbit inference engine (AGENTS.md)

**/*.rs: Keep functions small with single responsibility; apply command-query separation
Function documentation must include clear examples; test documentation should omit redundant examples
No single Rust code file may exceed 400 lines; split long switches/dispatch tables; move large test data to external files
Disallow Clippy warnings; code must be clean under clippy
Fix warnings emitted during tests in code, do not silence them
Extract helper functions when a function is too long; maintain separation of concerns and CQRS
Group many related parameters into well-named structs
Consider using Arc for large error values to reduce data returned
Every module must begin with a module-level //! comment describing purpose and utility
Document public APIs with Rustdoc /// comments so cargo doc can generate docs
Prefer immutable data; avoid unnecessary mut bindings
Use Result for error handling instead of panicking where feasible
Avoid unsafe code unless absolutely necessary; document any usage clearly
Place function attributes after doc comments
Do not use return in single-line functions
Use predicate functions for conditionals with more than two branches
Do not silence lints except as a last resort
When suppressing lints, scope narrowly and include a clear reason
Prefer #[expect(...)] over #[allow(...)] for lint handling
When functions are unused for certain features, gate with #[cfg]/#[cfg_attr]
Prefer .expect() over .unwrap()
Use concat!() for long string literals instead of escaping newlines with backslashes
Prefer single-line function bodies where appropriate (e.g., pub fn new(id: u64) -> Self { Self(id) })
Use cap-std for filesystem operations; avoid std::fs directly
Use camino for path handling; avoid std::path::PathBuf directly
Prefer semantic error enums deriving std::error::Error via thiserror for inspectable conditions
Use opaque eyre::Report only at the app boundary; do not expose in public APIs
Never export opaque error types from libraries; convert to domain...

Files:

  • tests/steps/mod.rs
  • src/stdlib/network.rs
  • src/stdlib/command.rs
  • tests/std_filter_tests/network_functions.rs
  • tests/steps/stdlib_steps.rs
  • tests/cucumber.rs
  • tests/std_filter_tests/command_filters.rs

⚙️ CodeRabbit configuration file

**/*.rs: * Seek to keep the cyclomatic complexity of functions no more than 12.

  • Adhere to single responsibility and CQRS

  • Place function attributes after doc comments.

  • Do not use return in single-line functions.

  • Move conditionals with >2 branches into a predicate function.

  • Avoid unsafe unless absolutely necessary.

  • Every module must begin with a //! doc comment that explains the module's purpose and utility.

  • Comments and docs must follow en-GB-oxendict (-ize / -yse / -our) spelling and grammar

  • Lints must not be silenced except as a last resort.

    • #[allow] is forbidden.
    • Only narrowly scoped #[expect(lint, reason = "...")] is allowed.
    • No lint groups, no blanket or file-wide suppression.
    • Include FIXME: with link if a fix is expected.
  • Where code is only used by specific features, it must be conditionally compiled or a conditional expectation for unused_code applied.

  • Use rstest fixtures for shared setup and to avoid repetition between tests.

  • Replace duplicated tests with #[rstest(...)] parameterised cases.

  • Prefer mockall for mocks/stubs.

  • Prefer .expect() over .unwrap()

  • Ensure that any API or behavioural changes are reflected in the documentation in docs/

  • Ensure that any completed roadmap steps are recorded in the appropriate roadmap in docs/

  • Files must not exceed 400 lines in length

    • Large modules must be decomposed
    • Long match statements or dispatch tables should be decomposed by domain and collocated with targets
    • Large blocks of inline data (e.g., test fixtures, constants or templates) must be moved to external files and inlined at compile-time or loaded at run-time.
  • Environment access (env::set_var and env::remove_var) are always unsafe in Rust 2024 and MUST be marked as such

    • For testing of functionality depending upon environment variables, dependency injection and the mockable crate are the preferred option.
    • If mockable cannot be used, env mutations in...

Files:

  • tests/steps/mod.rs
  • src/stdlib/network.rs
  • src/stdlib/command.rs
  • tests/std_filter_tests/network_functions.rs
  • tests/steps/stdlib_steps.rs
  • tests/cucumber.rs
  • tests/std_filter_tests/command_filters.rs
tests/**/*.rs

📄 CodeRabbit inference engine (AGENTS.md)

tests/**/*.rs: Write unit and behavioural tests for new functionality; run before and after changes
Use rstest fixtures for shared setup
Replace duplicated tests with #[rstest(...)] parameterised cases
Prefer mockall for mocks/stubs
Mock nondeterministic deps via dependency injection using mockable traits (e.g., Env, Clock); follow docs/reliable-testing-in-rust-via-dependency-injection.md

Files:

  • tests/steps/mod.rs
  • tests/std_filter_tests/network_functions.rs
  • tests/steps/stdlib_steps.rs
  • tests/cucumber.rs
  • tests/std_filter_tests/command_filters.rs
🧬 Code graph analysis (6)
src/stdlib/network.rs (1)
tests/std_filter_tests/command_filters.rs (2)
  • env (37-39)
  • new (21-35)
src/stdlib/command.rs (2)
src/stdlib/mod.rs (1)
  • register (69-79)
tests/std_filter_tests/command_filters.rs (4)
  • env (37-39)
  • state (45-47)
  • command (41-43)
  • new (21-35)
tests/std_filter_tests/network_functions.rs (2)
tests/std_filter_tests/support.rs (1)
  • stdlib_env_with_state (22-26)
src/stdlib/mod.rs (1)
  • is_impure (39-41)
tests/steps/stdlib_steps.rs (3)
test_support/src/command_helper.rs (2)
  • compile_failure_helper (68-70)
  • compile_uppercase_helper (46-48)
src/stdlib/command.rs (1)
  • register (56-74)
src/stdlib/mod.rs (2)
  • register (69-79)
  • is_impure (39-41)
tests/cucumber.rs (2)
tests/steps/stdlib_steps.rs (2)
  • spawn_http_server (106-125)
  • server_host (102-104)
test_support/src/env.rs (1)
  • restore_many (98-112)
tests/std_filter_tests/command_filters.rs (4)
test_support/src/command_helper.rs (3)
  • compile_failure_helper (68-70)
  • compile_uppercase_helper (46-48)
  • compile_rust_helper (98-114)
tests/std_filter_tests/support.rs (1)
  • stdlib_env_with_state (22-26)
src/stdlib/mod.rs (1)
  • is_impure (39-41)
test_support/src/env_lock.rs (1)
  • acquire (17-21)
⏰ 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: release / build-macos (aarch64-apple-darwin, macos-14, macos-arm64, arm64)
  • GitHub Check: release / build-macos (x86_64-apple-darwin, macos-13, macos-x86_64, x86_64)
  • GitHub Check: release / build-linux (x86_64-unknown-linux-gnu, linux-amd64, amd64)
  • GitHub Check: release / build-windows (aarch64-pc-windows-msvc, arm64, arm64)
  • GitHub Check: release / build-windows (x86_64-pc-windows-msvc, amd64, x64)
  • GitHub Check: build-test (ubuntu-latest, stable)
🔇 Additional comments (18)
tests/cucumber.rs (3)

66-72: LGTM!

The early return via let-else and handle cleanup is correct and idiomatic.


74-81: LGTM!

The helper successfully simplifies the conditional logic in Drop::drop and start_http_server, resolving the "Complex Conditional" code smell flagged in the PR objectives. The doc comment clearly explains the caller's usage.


83-89: LGTM!

The early return optimisation and drain-collect pattern are correct. Environment restoration is properly isolated via the test_support::env::restore_many helper.

src/stdlib/command.rs (15)

76-88: LGTM!

Input validation, error mapping, and command execution are correct. The trim check prevents empty commands from reaching the shell.


90-117: LGTM!

Pattern validation and platform-specific dispatch (direct grep invocation on Windows, shell command on non-Windows) are correct. The conditional compilation ensures proper quoting for each platform.


119-144: LGTM!

The match statement handles all relevant ValueKind variants: undefined values return empty args, sequences/iterables are mapped to strings, and scalar strings are wrapped in a vector. Error messages are clear.


146-164: LGTM!

Platform-specific quoting (Windows via windows_quote, non-Windows via POSIX quoting) ensures arguments with spaces or metacharacters are correctly escaped for each shell.


166-179: LGTM!

The undefined check prevents filter failures on missing values, and the fallback to to_string() ensures non-byte values are converted correctly.


181-190: LGTM!

Shell invocation with piped stdio is correct. The command string is passed as a single argument to the shell's -c or /C flag.


192-201: LGTM!

Direct program invocation on Windows (avoiding cmd.exe for grep) ensures arguments are not misinterpreted by the shell.


203-250: LGTM!

Process management is robust:

  • Broken pipe errors are caught and preserved (lines 205-217, 234-240), allowing the command to exit early if it rejects input (e.g., grep with invalid flags).
  • Pipe readers are spawned in separate threads (lines 219-220) to prevent deadlocks when stdout/stderr buffers fill.
  • Timeout handling (line 222) prevents indefinite hangs.
  • Reader handles are joined after the process exits (lines 231-232), ensuring all output is captured.

252-257: LGTM!

UTF-8 decoding with fallback to raw bytes ensures all command output is representable as a Value, regardless of encoding.


259-312: LGTM!

Error messages include full context (command, template, status/signal, stderr) for debugging. The broken pipe variant (lines 276-291) disambiguates early command exit from write failures.


314-333: LGTM!

The CommandFailure enum covers all failure modes: spawn errors, I/O errors, broken pipes (with status and stderr), exit failures, and timeouts. The From<io::Error> implementation simplifies error propagation.


335-347: LGTM!

Timeout handling is correct: if wait_timeout returns None (timeout), the child is killed and Timeout is returned. The InvalidInput error check (line 340) correctly handles the case where the process has already exited.


349-371: LGTM!

Pipe readers are spawned in separate threads to prevent deadlocks. The join logic (lines 363-370) handles thread panics by mapping them to I/O errors.


373-380: LGTM!

The stderr trimming and conditional append ensure error messages include diagnostic output without excessive whitespace.


362-371: Confirm MSRV supports io::Error::other. Cargo.toml sets rust-version = "1.89.0" (≥1.74), so no replacement required.

Comment on lines +56 to +74
pub(crate) fn register(env: &mut minijinja::Environment<'_>, impure: Arc<AtomicBool>) {
let shell_flag = Arc::clone(&impure);
env.add_filter(
"shell",
move |state: &State, value: Value, command: String| {
shell_flag.store(true, Ordering::Relaxed);
execute_shell(state, &value, &command)
},
);

let grep_flag = impure;
env.add_filter(
"grep",
move |state: &State, value: Value, pattern: String, flags: Option<Value>| {
grep_flag.store(true, Ordering::Relaxed);
execute_grep(state, &value, &pattern, flags)
},
);
}
Copy link
Contributor

Choose a reason for hiding this comment

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

🧹 Nitpick | 🔵 Trivial

Add doc comment for register function.

The coding guidelines require that public APIs (including pub(crate) functions) have doc comments so cargo doc can generate documentation. Document the purpose of this function, the impure flag's role in marking templates impure, and the filters it registers.

Add this doc comment:

+/// Registers shell-oriented filters in the MiniJinja environment.
+///
+/// The `shell` filter executes arbitrary shell commands and returns their
+/// stdout. The `grep` filter searches input text using the `grep` utility.
+/// Both filters mark the provided `impure` flag to signal that template
+/// evaluation has side effects and should not be cached.
+///
+/// # Security
+///
+/// Only use these filters with trusted templates. See module-level docs.
 pub(crate) fn register(env: &mut minijinja::Environment<'_>, impure: Arc<AtomicBool>) {
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
pub(crate) fn register(env: &mut minijinja::Environment<'_>, impure: Arc<AtomicBool>) {
let shell_flag = Arc::clone(&impure);
env.add_filter(
"shell",
move |state: &State, value: Value, command: String| {
shell_flag.store(true, Ordering::Relaxed);
execute_shell(state, &value, &command)
},
);
let grep_flag = impure;
env.add_filter(
"grep",
move |state: &State, value: Value, pattern: String, flags: Option<Value>| {
grep_flag.store(true, Ordering::Relaxed);
execute_grep(state, &value, &pattern, flags)
},
);
}
/// Registers shell-oriented filters in the MiniJinja environment.
///
/// The `shell` filter executes arbitrary shell commands and returns their
/// stdout. The `grep` filter searches input text using the `grep` utility.
/// Both filters mark the provided `impure` flag to signal that template
/// evaluation has side effects and should not be cached.
///
/// # Security
///
/// Only use these filters with trusted templates. See module-level docs.
pub(crate) fn register(env: &mut minijinja::Environment<'_>, impure: Arc<AtomicBool>) {
let shell_flag = Arc::clone(&impure);
env.add_filter(
"shell",
move |state: &State, value: Value, command: String| {
shell_flag.store(true, Ordering::Relaxed);
execute_shell(state, &value, &command)
},
);
let grep_flag = impure;
env.add_filter(
"grep",
move |state: &State, value: Value, pattern: String, flags: Option<Value>| {
grep_flag.store(true, Ordering::Relaxed);
execute_grep(state, &value, &pattern, flags)
},
);
}
🤖 Prompt for AI Agents
In src/stdlib/command.rs around lines 56 to 74, the register function lacks a
doc comment; add a triple-slash (///) Rust doc comment immediately above
pub(crate) fn register explaining that this function registers the "shell" and
"grep" Minijinja filters on the provided Environment, describing the role of the
impure Arc<AtomicBool> parameter (it is set true when either filter is used to
mark templates as impure), and briefly summarizing behavior of each registered
filter (shell executes shell commands, grep filters values by pattern and
optional flags); keep the comment concise and idiomatic for cargo doc
generation.

Comment on lines +55 to +64
impl CliWorld {
pub(crate) fn start_http_server(&mut self, body: String) {
if let Some(host) = self.extract_host_from_stdlib_url() {
let _ = TcpStream::connect(host);
}
self.shutdown_http_server();
let (url, handle) = spawn_http_server(body);
self.stdlib_url = Some(url);
self.http_server = Some(handle);
}
Copy link
Contributor

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟠 Major

Guard against hangs if URL parsing fails.

The unblocking logic (lines 57-59) assumes that when http_server is Some, extract_host_from_stdlib_url succeeds. If URL parsing fails unexpectedly (e.g., malformed URL from a future refactor), shutdown_http_server (line 60) calls join() without unblocking the listener, causing the test suite to hang indefinitely. Add a defensive check: only call shutdown_http_server if the host extraction succeeds, or .expect() on the host extraction with a clear panic message that pinpoints the root cause.

Apply this diff to fail fast on URL parsing errors:

 pub(crate) fn start_http_server(&mut self, body: String) {
     if let Some(host) = self.extract_host_from_stdlib_url() {
         let _ = TcpStream::connect(host);
+    } else if self.http_server.is_some() {
+        panic!(
+            "Cannot extract host from stdlib_url; server teardown will hang. URL: {:?}",
+            self.stdlib_url
+        );
     }
     self.shutdown_http_server();
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
impl CliWorld {
pub(crate) fn start_http_server(&mut self, body: String) {
if let Some(host) = self.extract_host_from_stdlib_url() {
let _ = TcpStream::connect(host);
}
self.shutdown_http_server();
let (url, handle) = spawn_http_server(body);
self.stdlib_url = Some(url);
self.http_server = Some(handle);
}
impl CliWorld {
pub(crate) fn start_http_server(&mut self, body: String) {
if let Some(host) = self.extract_host_from_stdlib_url() {
let _ = TcpStream::connect(host);
} else if self.http_server.is_some() {
panic!(
"Cannot extract host from stdlib_url; server teardown will hang. URL: {:?}",
self.stdlib_url
);
}
self.shutdown_http_server();
let (url, handle) = spawn_http_server(body);
self.stdlib_url = Some(url);
self.http_server = Some(handle);
}
}
🤖 Prompt for AI Agents
In tests/cucumber.rs around lines 55–64, the current code calls
shutdown_http_server unconditionally after attempting to connect to the host
extracted from stdlib_url, which can hang if extract_host_from_stdlib_url fails;
update the logic so you either (A) only call shutdown_http_server inside the
same if-let block that succeeded in extracting the host (i.e., move the shutdown
call into that branch) or (B) change the host extraction to use .expect("failed
to parse stdlib_url host for unblocking; ensure stdlib_url is valid") so the
test fails fast with a clear message if URL parsing fails.

Comment on lines 105 to 113
impl Drop for CliWorld {
fn drop(&mut self) {
if !self.env_vars.is_empty() {
restore_many(self.env_vars.drain().collect());
if let Some(host) = self.extract_host_from_stdlib_url() {
let _ = TcpStream::connect(host);
}
self.shutdown_http_server();
self.restore_environment();
}
}
Copy link
Contributor

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🔴 Critical

Guard against hangs in Drop.

The same issue from start_http_server applies here: if extract_host_from_stdlib_url returns None whilst http_server is Some, shutdown_http_server (line 110) calls join() without unblocking the listener, causing the destructor to hang indefinitely. Since Drop::drop cannot panic (undefined behaviour), add a defensive check: log a warning or skip shutdown entirely if host extraction fails.

Apply this diff to prevent hangs in Drop:

 impl Drop for CliWorld {
     fn drop(&mut self) {
         if let Some(host) = self.extract_host_from_stdlib_url() {
             let _ = TcpStream::connect(host);
+        } else if self.http_server.is_some() {
+            eprintln!(
+                "Warning: Cannot extract host from stdlib_url; skipping server shutdown to avoid hang. URL: {:?}",
+                self.stdlib_url
+            );
+            // Leak the handle rather than blocking forever
+            let _ = self.http_server.take();
+            self.restore_environment();
+            return;
         }
         self.shutdown_http_server();
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
impl Drop for CliWorld {
fn drop(&mut self) {
if !self.env_vars.is_empty() {
restore_many(self.env_vars.drain().collect());
if let Some(host) = self.extract_host_from_stdlib_url() {
let _ = TcpStream::connect(host);
}
self.shutdown_http_server();
self.restore_environment();
}
}
impl Drop for CliWorld {
fn drop(&mut self) {
if let Some(host) = self.extract_host_from_stdlib_url() {
let _ = TcpStream::connect(host);
} else if self.http_server.is_some() {
eprintln!(
"Warning: cannot extract host from stdlib_url; skipping server shutdown to avoid hang. URL: {:?}",
self.stdlib_url
);
// Leak the handle rather than blocking forever
let _ = self.http_server.take();
self.restore_environment();
return;
}
self.shutdown_http_server();
self.restore_environment();
}
}
🤖 Prompt for AI Agents
In tests/cucumber.rs around lines 105 to 113, the Drop impl for CliWorld can
hang if extract_host_from_stdlib_url() returns None while http_server is Some
because shutdown_http_server() will call join() without unblocking the listener;
change the Drop logic to first check extract_host_from_stdlib_url(), and only
attempt the TcpStream::connect + shutdown_http_server() sequence when you get
Some(host); if extract_host_from_stdlib_url() returns None, log a warning and
skip calling shutdown_http_server() (do not panic) so the destructor cannot
block indefinitely.

Comment on lines +204 to +207
#[allow(
clippy::collapsible_if,
reason = "Review requested explicit nested host extraction checks"
)]
Copy link
Contributor

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟠 Major

Replace the forbidden #[allow] with #[expect].

Stop using #[allow] here; the coding rules demand #[expect(..., reason = "...")] for lint handling.

Apply this diff to comply:

-#[allow(
+#[expect(
     clippy::collapsible_if,
     reason = "Review requested explicit nested host extraction checks"
 )]

As per coding guidelines

📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
#[allow(
clippy::collapsible_if,
reason = "Review requested explicit nested host extraction checks"
)]
#[expect(
clippy::collapsible_if,
reason = "Review requested explicit nested host extraction checks"
)]
🤖 Prompt for AI Agents
In tests/steps/stdlib_steps.rs around lines 204 to 207, the code uses
#[allow(clippy::collapsible_if, reason = "Review requested explicit nested host
extraction checks")]; replace this forbidden #[allow] with
#[expect(clippy::collapsible_if, reason = "Review requested explicit nested host
extraction checks")] so the lint is handled via #[expect] with the same lint
name and reason; update the attribute accordingly and ensure formatting matches
surrounding attributes.

* Use Path.open in Windows output normaliser

* Parametrise staging config validation tests

* Refactor staging tests setup

* Normalise CLI inputs and safe staging logging

* Extract staging duplicate validation helpers

* Re-export glob helpers from staging

* Simplify release uploader CLI fallbacks
Copy link

@codescene-delta-analysis codescene-delta-analysis bot left a comment

Choose a reason for hiding this comment

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

Gates Failed
Enforce critical code health rules (1 file with Bumpy Road Ahead)
Enforce advisory code health rules (6 files with Complex Method, Excess Number of Function Arguments, Code Duplication)

Gates Passed
4 Quality Gates Passed

See analysis details in CodeScene

Reason for failure
Enforce critical code health rules Violations Code Health Impact
glob_utils.py 1 critical rule 9.54 Suppress
Enforce advisory code health rules Violations Code Health Impact
test_stage_common_staging.py 2 advisory rules 9.10 Suppress
glob_utils.py 1 advisory rule 9.54 Suppress
staging.py 1 advisory rule 9.54 Suppress
config.py 1 advisory rule 9.69 Suppress
_release_upload_deps.py 1 advisory rule 9.69 Suppress
test_stage_common_config.py 1 advisory rule 9.69 Suppress

Quality Gate Profile: Pay Down Tech Debt
Want more control? Customize Code Health rules or catch issues early with our IDE extension and CLI tool.

Comment on lines +343 to +368
def _normalise_alternatives(
value: object, index: int, config_path: Path
) -> list[str]:
"""Return ``value`` as a list of alternative glob patterns."""

if value is None:
return []
if isinstance(value, str):
return [value] if value else []
if not isinstance(value, list):
message = (
"Alternatives must be a list of strings "
f"(entry #{index} in {config_path})"
)
raise StageError(message)
alternatives: list[str] = []
for alternative in value:
if not isinstance(alternative, str):
message = (
"Alternatives must be strings "
f"(entry #{index} in {config_path})"
)
raise StageError(message)
if alternative:
alternatives.append(alternative)
return alternatives

Choose a reason for hiding this comment

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

❌ New issue: Complex Method
_normalise_alternatives has a cyclomatic complexity of 9, threshold = 9

Suppress

Comment on lines +10 to +35
def match_candidate_path(workspace: Path, rendered: str) -> Path | None:
"""Return the newest path matching ``rendered`` relative to ``workspace``."""

candidate = Path(rendered)
base = candidate if candidate.is_absolute() else workspace / candidate
if any(ch in rendered for ch in "*?[]"):
if candidate.is_absolute():
root_text, pattern = glob_root_and_pattern(candidate)
root = Path(root_text)
candidates = [
path for path in root.glob(pattern) if path.is_file()
]
else:
windows_candidate = PureWindowsPath(rendered)
if windows_candidate.is_absolute():
anchor = Path(windows_candidate.anchor)
relative = PureWindowsPath(*windows_candidate.parts[1:]).as_posix()
candidates = [
path for path in anchor.glob(relative) if path.is_file()
]
else:
candidates = [
path for path in workspace.glob(rendered) if path.is_file()
]
return max(candidates, key=_mtime_key) if candidates else None
return base if base.is_file() else None

Choose a reason for hiding this comment

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

❌ New issue: Complex Method
match_candidate_path has a cyclomatic complexity of 10, threshold = 9

Suppress

Comment on lines +10 to +35
def match_candidate_path(workspace: Path, rendered: str) -> Path | None:
"""Return the newest path matching ``rendered`` relative to ``workspace``."""

candidate = Path(rendered)
base = candidate if candidate.is_absolute() else workspace / candidate
if any(ch in rendered for ch in "*?[]"):
if candidate.is_absolute():
root_text, pattern = glob_root_and_pattern(candidate)
root = Path(root_text)
candidates = [
path for path in root.glob(pattern) if path.is_file()
]
else:
windows_candidate = PureWindowsPath(rendered)
if windows_candidate.is_absolute():
anchor = Path(windows_candidate.anchor)
relative = PureWindowsPath(*windows_candidate.parts[1:]).as_posix()
candidates = [
path for path in anchor.glob(relative) if path.is_file()
]
else:
candidates = [
path for path in workspace.glob(rendered) if path.is_file()
]
return max(candidates, key=_mtime_key) if candidates else None
return base if base.is_file() else None

Choose a reason for hiding this comment

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

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

Suppress

Comment on lines +119 to +214
def stage_artefacts(config: StagingConfig, github_output_file: Path) -> StageResult:
"""Copy artefacts into ``config``'s staging directory.

Parameters
----------
config : StagingConfig
Fully resolved configuration describing the artefacts to stage.
github_output_file : Path
Path to the ``GITHUB_OUTPUT`` file used to export workflow outputs.

Returns
-------
StageResult
Summary object describing the staging directory, staged artefacts,
exported outputs, and checksum digests.

Raises
------
StageError
Raised when required artefacts are missing or configuration templates
render invalid destinations.

Examples
--------
>>> from pathlib import Path # doctest: +SKIP
>>> from stage_common.config import load_config # doctest: +SKIP
>>> cfg = load_config( # doctest: +SKIP
... Path(".github/release-staging.toml"),
... "linux-x86_64",
... )
>>> result = stage_artefacts(cfg, Path("github_output.txt")) # doctest: +SKIP
>>> result.staged_artefacts[0].name # doctest: +SKIP
'netsuke'
"""
staging_dir = config.staging_dir()
context = config.as_template_context()

if staging_dir.exists():
shutil.rmtree(staging_dir)
staging_dir.mkdir(parents=True, exist_ok=True)

staged_paths: list[Path] = []
outputs: dict[str, Path] = {}
checksums: dict[str, str] = {}
seen_destinations: set[Path] = set()
seen_outputs: set[str] = set()

for artefact in config.artefacts:
outcome = _stage_single_artefact(
config=config,
artefact=artefact,
staging_dir=staging_dir,
context=context,
)
if outcome is None:
continue
_validate_and_record_destination(
outcome.path, artefact.source, seen_destinations
)
staged_paths.append(outcome.path)
checksums[outcome.path.name] = outcome.digest
if outcome.output_key:
_validate_and_record_output(
outcome.output_key, outcome.path, seen_outputs, outputs
)

if not staged_paths:
message = "No artefacts were staged."
raise StageError(message)

staged_file_names = [path.name for path in sorted(staged_paths)]
artefact_map_json = json.dumps(
{key: path.as_posix() for key, path in sorted(outputs.items())}
)
checksum_map_json = json.dumps(dict(sorted(checksums.items())))

if colliding_keys := RESERVED_OUTPUT_KEYS & outputs.keys():
message = (
"Artefact outputs collide with reserved keys: "
f"{sorted(colliding_keys)}. Please rename these keys in your "
"artefact configuration to avoid using reserved names: "
f"{sorted(RESERVED_OUTPUT_KEYS)}."
)
raise StageError(message)

exported_outputs: dict[str, str | list[str]] = {
"artifact_dir": staging_dir.as_posix(),
"dist_dir": staging_dir.parent.as_posix(),
"staged_files": staged_file_names,
"artefact_map": artefact_map_json,
"checksum_map": checksum_map_json,
} | {key: path.as_posix() for key, path in outputs.items()}

write_github_output(github_output_file, exported_outputs)

return StageResult(staging_dir, staged_paths, outputs, checksums)

Choose a reason for hiding this comment

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

❌ New issue: Complex Method
stage_artefacts has a cyclomatic complexity of 12, threshold = 9

Suppress

Comment on lines +96 to +142
def run_cli(
support: CycloptsSupport,
*,
coerce_bool: Callable[[object], bool],
main: Callable[..., int],
tokens: Sequence[str] | None = None,
) -> int:
"""Execute the uploader CLI using Cyclopts or ``argparse`` fallback."""

arguments = list(tokens) if tokens is not None else None

if support.available:
return support.app(arguments)

parser = ArgumentParser(description=main.__doc__ or "Upload release assets.")
parser.add_argument("--release-tag")
parser.add_argument("--bin-name")
parser.add_argument("--dist-dir")
parser.add_argument("--dry-run", action="store_true")
args = parser.parse_args(arguments if arguments is not None else sys.argv[1:])

release_tag = args.release_tag or os.environ.get("INPUT_RELEASE_TAG")
bin_name = args.bin_name or os.environ.get("INPUT_BIN_NAME")
dist_dir_value = args.dist_dir or os.environ.get("INPUT_DIST_DIR") or "dist"
dry_run_flag = args.dry_run

if not dry_run_flag and (env_flag := os.environ.get("INPUT_DRY_RUN")):
dry_run_flag = coerce_bool(env_flag)

missing = [
label
for label, present in (
("--release-tag", release_tag),
("--bin-name", bin_name),
)
if not present
]
if missing:
joined = ", ".join(missing)
parser.exit(status=1, message=f"Missing required argument(s): {joined}\n")

return main(
release_tag=release_tag,
bin_name=bin_name,
dist_dir=Path(dist_dir_value),
dry_run=dry_run_flag,
)

Choose a reason for hiding this comment

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

❌ New issue: Complex Method
run_cli has a cyclomatic complexity of 11, threshold = 9

Suppress

Comment on lines +200 to +223
def test_load_config_validation_errors(
stage_common: object,
workspace: Path,
config_content: str,
error_substring: str,
test_id: str,
) -> None:
"""``load_config`` should raise ``StageError`` for invalid configurations."""

config_file = workspace / "release-staging.toml"
config_file.write_text(config_content, encoding="utf-8")

with pytest.raises(stage_common.StageError) as exc:
stage_common.load_config(config_file, "test")

message = str(exc.value)
assert error_substring in message, f"{test_id} missing expected substring"

if test_id == "requires_common_bin_name":
assert "[common]" in message
elif test_id == "requires_target_platform":
assert "[targets.test]" in message
elif test_id == "requires_artefact_source":
assert "entry #1" in message

Choose a reason for hiding this comment

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

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

Suppress

Comment on lines +107 to +152
def test_stage_artefacts_uses_alternative_glob(
stage_common: object, prepared_workspace: Path, standard_target: str
) -> None:
"""Fallback paths should be used when the preferred template is absent."""

generated = (
prepared_workspace
/ "target"
/ "generated-man"
/ standard_target
/ "release"
/ "netsuke.1"
)
generated.unlink()

build_dir = (
prepared_workspace / "target" / standard_target / "release" / "build"
)
first = build_dir / "1" / "out" / "netsuke.1"
second = build_dir / "2" / "out" / "netsuke.1"
first.parent.mkdir(parents=True, exist_ok=True)
second.parent.mkdir(parents=True, exist_ok=True)
first.write_text(".TH 1", encoding="utf-8")
second.write_text(".TH 2", encoding="utf-8")
os.utime(first, (first.stat().st_atime, first.stat().st_mtime - 100))

config = create_staging_config(
stage_common,
prepared_workspace,
artefacts=[
stage_common.ArtefactConfig(
source="target/generated-man/{target}/release/{bin_name}.1",
required=True,
output="man_path",
alternatives=["target/{target}/release/build/*/out/{bin_name}.1"],
),
],
target=standard_target,
)

github_output = prepared_workspace / "outputs.txt"
result = stage_common.stage_artefacts(config, github_output)
staged_path = result.outputs["man_path"]
assert (
staged_path.read_text(encoding="utf-8") == ".TH 2"
), "Fallback glob should pick the newest man page"

Choose a reason for hiding this comment

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

❌ New issue: Code Duplication
The module contains 2 functions with similar structure: test_stage_artefacts_glob_selects_newest_candidate,test_stage_artefacts_uses_alternative_glob

Suppress

Comment on lines +304 to +326
def test_stage_artefacts_validation_errors(
stage_common: object,
prepared_workspace: Path,
standard_target: str,
artefact_definitions: list[dict[str, object]],
expected_match: str,
) -> None:
"""Staging should fail fast when reserved names or duplicates are present."""

artefacts = [
stage_common.ArtefactConfig(**definition)
for definition in artefact_definitions
]

config = create_staging_config(
stage_common,
prepared_workspace,
artefacts=artefacts,
target=standard_target,
)

with pytest.raises(stage_common.StageError, match=expected_match):
stage_common.stage_artefacts(config, prepared_workspace / "outputs.txt")

Choose a reason for hiding this comment

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

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

Suppress

Copy link
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: 9

📜 Review details

Configuration used: CodeRabbit UI

Review profile: ASSERTIVE

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between ff68165 and 2addbee.

📒 Files selected for processing (7)
  • src/stdlib/command.rs (1 hunks)
  • src/stdlib/network.rs (1 hunks)
  • tests/cucumber.rs (3 hunks)
  • tests/std_filter_tests/command_filters.rs (1 hunks)
  • tests/std_filter_tests/network_functions.rs (1 hunks)
  • tests/steps/mod.rs (1 hunks)
  • tests/steps/stdlib_steps.rs (6 hunks)
🧰 Additional context used
📓 Path-based instructions (2)
**/*.rs

📄 CodeRabbit inference engine (AGENTS.md)

**/*.rs: Keep functions small with single responsibility; apply command-query separation
Function documentation must include clear examples; test documentation should omit redundant examples
No single Rust code file may exceed 400 lines; split long switches/dispatch tables; move large test data to external files
Disallow Clippy warnings; code must be clean under clippy
Fix warnings emitted during tests in code, do not silence them
Extract helper functions when a function is too long; maintain separation of concerns and CQRS
Group many related parameters into well-named structs
Consider using Arc for large error values to reduce data returned
Every module must begin with a module-level //! comment describing purpose and utility
Document public APIs with Rustdoc /// comments so cargo doc can generate docs
Prefer immutable data; avoid unnecessary mut bindings
Use Result for error handling instead of panicking where feasible
Avoid unsafe code unless absolutely necessary; document any usage clearly
Place function attributes after doc comments
Do not use return in single-line functions
Use predicate functions for conditionals with more than two branches
Do not silence lints except as a last resort
When suppressing lints, scope narrowly and include a clear reason
Prefer #[expect(...)] over #[allow(...)] for lint handling
When functions are unused for certain features, gate with #[cfg]/#[cfg_attr]
Prefer .expect() over .unwrap()
Use concat!() for long string literals instead of escaping newlines with backslashes
Prefer single-line function bodies where appropriate (e.g., pub fn new(id: u64) -> Self { Self(id) })
Use cap-std for filesystem operations; avoid std::fs directly
Use camino for path handling; avoid std::path::PathBuf directly
Prefer semantic error enums deriving std::error::Error via thiserror for inspectable conditions
Use opaque eyre::Report only at the app boundary; do not expose in public APIs
Never export opaque error types from libraries; convert to domain...

Files:

  • src/stdlib/command.rs
  • tests/std_filter_tests/command_filters.rs
  • src/stdlib/network.rs
  • tests/steps/mod.rs
  • tests/std_filter_tests/network_functions.rs
  • tests/steps/stdlib_steps.rs
  • tests/cucumber.rs

⚙️ CodeRabbit configuration file

**/*.rs: * Seek to keep the cyclomatic complexity of functions no more than 12.

  • Adhere to single responsibility and CQRS

  • Place function attributes after doc comments.

  • Do not use return in single-line functions.

  • Move conditionals with >2 branches into a predicate function.

  • Avoid unsafe unless absolutely necessary.

  • Every module must begin with a //! doc comment that explains the module's purpose and utility.

  • Comments and docs must follow en-GB-oxendict (-ize / -yse / -our) spelling and grammar

  • Lints must not be silenced except as a last resort.

    • #[allow] is forbidden.
    • Only narrowly scoped #[expect(lint, reason = "...")] is allowed.
    • No lint groups, no blanket or file-wide suppression.
    • Include FIXME: with link if a fix is expected.
  • Where code is only used by specific features, it must be conditionally compiled or a conditional expectation for unused_code applied.

  • Use rstest fixtures for shared setup and to avoid repetition between tests.

  • Replace duplicated tests with #[rstest(...)] parameterised cases.

  • Prefer mockall for mocks/stubs.

  • Prefer .expect() over .unwrap()

  • Ensure that any API or behavioural changes are reflected in the documentation in docs/

  • Ensure that any completed roadmap steps are recorded in the appropriate roadmap in docs/

  • Files must not exceed 400 lines in length

    • Large modules must be decomposed
    • Long match statements or dispatch tables should be decomposed by domain and collocated with targets
    • Large blocks of inline data (e.g., test fixtures, constants or templates) must be moved to external files and inlined at compile-time or loaded at run-time.
  • Environment access (env::set_var and env::remove_var) are always unsafe in Rust 2024 and MUST be marked as such

    • For testing of functionality depending upon environment variables, dependency injection and the mockable crate are the preferred option.
    • If mockable cannot be used, env mutations in...

Files:

  • src/stdlib/command.rs
  • tests/std_filter_tests/command_filters.rs
  • src/stdlib/network.rs
  • tests/steps/mod.rs
  • tests/std_filter_tests/network_functions.rs
  • tests/steps/stdlib_steps.rs
  • tests/cucumber.rs
tests/**/*.rs

📄 CodeRabbit inference engine (AGENTS.md)

tests/**/*.rs: Write unit and behavioural tests for new functionality; run before and after changes
Use rstest fixtures for shared setup
Replace duplicated tests with #[rstest(...)] parameterised cases
Prefer mockall for mocks/stubs
Mock nondeterministic deps via dependency injection using mockable traits (e.g., Env, Clock); follow docs/reliable-testing-in-rust-via-dependency-injection.md

Files:

  • tests/std_filter_tests/command_filters.rs
  • tests/steps/mod.rs
  • tests/std_filter_tests/network_functions.rs
  • tests/steps/stdlib_steps.rs
  • tests/cucumber.rs
🧬 Code graph analysis (6)
src/stdlib/command.rs (2)
src/stdlib/mod.rs (1)
  • register (69-79)
tests/std_filter_tests/command_filters.rs (4)
  • env (37-39)
  • state (45-47)
  • command (41-43)
  • new (21-35)
tests/std_filter_tests/command_filters.rs (4)
test_support/src/command_helper.rs (3)
  • compile_failure_helper (68-70)
  • compile_uppercase_helper (46-48)
  • compile_rust_helper (98-114)
tests/std_filter_tests/support.rs (1)
  • stdlib_env_with_state (22-26)
src/stdlib/mod.rs (1)
  • is_impure (39-41)
test_support/src/env_lock.rs (1)
  • acquire (17-21)
src/stdlib/network.rs (1)
tests/std_filter_tests/command_filters.rs (2)
  • env (37-39)
  • new (21-35)
tests/std_filter_tests/network_functions.rs (2)
tests/std_filter_tests/support.rs (1)
  • stdlib_env_with_state (22-26)
src/stdlib/mod.rs (1)
  • is_impure (39-41)
tests/steps/stdlib_steps.rs (3)
test_support/src/command_helper.rs (2)
  • compile_failure_helper (68-70)
  • compile_uppercase_helper (46-48)
src/stdlib/command.rs (1)
  • register (56-74)
src/stdlib/mod.rs (2)
  • register (69-79)
  • is_impure (39-41)
tests/cucumber.rs (2)
tests/steps/stdlib_steps.rs (2)
  • spawn_http_server (106-125)
  • server_host (102-104)
test_support/src/env.rs (1)
  • restore_many (98-112)
⏰ 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). (7)
  • GitHub Check: release / build-macos (aarch64-apple-darwin, macos-14, macos-arm64, arm64)
  • GitHub Check: release / build-macos (x86_64-apple-darwin, macos-13, macos-x86_64, x86_64)
  • GitHub Check: release / build-linux (aarch64-unknown-linux-gnu, linux-arm64, arm64)
  • GitHub Check: release / build-linux (x86_64-unknown-linux-gnu, linux-amd64, amd64)
  • GitHub Check: release / build-windows (aarch64-pc-windows-msvc, arm64, arm64)
  • GitHub Check: release / build-windows (x86_64-pc-windows-msvc, amd64, x64)
  • GitHub Check: build-test (ubuntu-latest, stable)
🔇 Additional comments (4)
tests/steps/mod.rs (1)

13-13: LGTM

Expose stdlib_steps to the crate; matches the new shared state usage.

tests/steps/stdlib_steps.rs (3)

106-125: LGTM: Avoid panics/noisy logs in server thread.

Only write a response when bytes were read and ignore write errors; this unblocks zero‑byte connects cleanly.


106-126: Keep spawn_http_server; it’s used in tests/cucumber.rs.


239-253: Resolve env mutation helper guards mutations
test_support::env::set_var acquires EnvLock to serialise environment changes, adhering to test safety policy.

Comment on lines +203 to +250
fn run_child(mut command: Command, input: &[u8]) -> Result<Vec<u8>, CommandFailure> {
let mut child = command.spawn().map_err(CommandFailure::Spawn)?;
let mut broken_pipe = None;
if let Some(mut stdin) = child.stdin.take() {
match stdin.write_all(input) {
Ok(()) => {}
Err(err) => {
if err.kind() == io::ErrorKind::BrokenPipe {
broken_pipe = Some(err);
} else {
return Err(CommandFailure::Io(err));
}
}
}
}

let mut stdout_reader = spawn_pipe_reader(child.stdout.take());
let mut stderr_reader = spawn_pipe_reader(child.stderr.take());

let status = match wait_for_exit(&mut child, COMMAND_TIMEOUT) {
Ok(status) => status,
Err(err) => {
let _ = join_reader(stdout_reader.take());
let _ = join_reader(stderr_reader.take());
return Err(err);
}
};

let stdout = join_reader(stdout_reader.take()).map_err(CommandFailure::Io)?;
let stderr = join_reader(stderr_reader.take()).map_err(CommandFailure::Io)?;

if let Some(err) = broken_pipe {
return Err(CommandFailure::BrokenPipe {
source: err,
status: status.code(),
stderr,
});
}

if status.success() {
Ok(stdout)
} else {
Err(CommandFailure::Exit {
status: status.code(),
stderr,
})
}
}
Copy link
Contributor

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🔴 Critical

Eliminate deadlock when writing to child stdin

Do not block on stdin.write_all before starting readers and before applying the timeout. A child that doesn’t read stdin and writes enough to stdout/stderr can deadlock this thread forever (timeout never triggers). Spawn stdout/stderr readers first and write on a dedicated thread; always join the writer and readers on exit/timeout.

Apply this diff:

@@
-fn run_child(mut command: Command, input: &[u8]) -> Result<Vec<u8>, CommandFailure> {
-    let mut child = command.spawn().map_err(CommandFailure::Spawn)?;
-    let mut broken_pipe = None;
-    if let Some(mut stdin) = child.stdin.take() {
-        match stdin.write_all(input) {
-            Ok(()) => {}
-            Err(err) => {
-                if err.kind() == io::ErrorKind::BrokenPipe {
-                    broken_pipe = Some(err);
-                } else {
-                    return Err(CommandFailure::Io(err));
-                }
-            }
-        }
-    }
-
-    let mut stdout_reader = spawn_pipe_reader(child.stdout.take());
-    let mut stderr_reader = spawn_pipe_reader(child.stderr.take());
+fn run_child(mut command: Command, input: &[u8]) -> Result<Vec<u8>, CommandFailure> {
+    let mut child = command.spawn().map_err(CommandFailure::Spawn)?;
+    // Start readers first to avoid pipe back‑pressure deadlocks.
+    let mut stdout_reader = spawn_pipe_reader(child.stdout.take());
+    let mut stderr_reader = spawn_pipe_reader(child.stderr.take());
+    // Write stdin on a separate thread so a non‑reading child cannot block us.
+    let stdin_handle = child.stdin.take().map(|mut stdin| {
+        let buf = input.to_vec();
+        thread::spawn(move || {
+            let res = stdin.write_all(&buf);
+            drop(stdin); // close to signal EOF
+            res
+        })
+    });
@@
-    let status = match wait_for_exit(&mut child, COMMAND_TIMEOUT) {
+    let status = match wait_for_exit(&mut child, COMMAND_TIMEOUT) {
         Ok(status) => status,
         Err(err) => {
-            let _ = join_reader(stdout_reader.take());
-            let _ = join_reader(stderr_reader.take());
+            let _ = join_reader(stdout_reader.take());
+            let _ = join_reader(stderr_reader.take());
+            if let Some(handle) = stdin_handle {
+                let _ = handle.join();
+            }
             return Err(err);
         }
     };
 
-    let stdout = join_reader(stdout_reader.take()).map_err(CommandFailure::Io)?;
-    let stderr = join_reader(stderr_reader.take()).map_err(CommandFailure::Io)?;
-
-    if let Some(err) = broken_pipe {
-        return Err(CommandFailure::BrokenPipe {
-            source: err,
-            status: status.code(),
-            stderr,
-        });
-    }
+    let stdout = join_reader(stdout_reader.take()).map_err(CommandFailure::Io)?;
+    let stderr = join_reader(stderr_reader.take()).map_err(CommandFailure::Io)?;
+    if let Some(handle) = stdin_handle {
+        match handle.join() {
+            Ok(Ok(())) => {}
+            Ok(Err(err)) => {
+                if err.kind() == io::ErrorKind::BrokenPipe {
+                    return Err(CommandFailure::BrokenPipe {
+                        source: err,
+                        status: status.code(),
+                        stderr,
+                    });
+                }
+                return Err(CommandFailure::Io(err));
+            }
+            Err(_) => return Err(CommandFailure::Io(io::Error::other("stdin writer panicked"))),
+        }
+    }
@@
-        Err(CommandFailure::Exit {
-            status: status.code(),
-            stderr,
-        })
+        Err(CommandFailure::Exit { status: status.code(), stderr })
     }
 }
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
fn run_child(mut command: Command, input: &[u8]) -> Result<Vec<u8>, CommandFailure> {
let mut child = command.spawn().map_err(CommandFailure::Spawn)?;
let mut broken_pipe = None;
if let Some(mut stdin) = child.stdin.take() {
match stdin.write_all(input) {
Ok(()) => {}
Err(err) => {
if err.kind() == io::ErrorKind::BrokenPipe {
broken_pipe = Some(err);
} else {
return Err(CommandFailure::Io(err));
}
}
}
}
let mut stdout_reader = spawn_pipe_reader(child.stdout.take());
let mut stderr_reader = spawn_pipe_reader(child.stderr.take());
let status = match wait_for_exit(&mut child, COMMAND_TIMEOUT) {
Ok(status) => status,
Err(err) => {
let _ = join_reader(stdout_reader.take());
let _ = join_reader(stderr_reader.take());
return Err(err);
}
};
let stdout = join_reader(stdout_reader.take()).map_err(CommandFailure::Io)?;
let stderr = join_reader(stderr_reader.take()).map_err(CommandFailure::Io)?;
if let Some(err) = broken_pipe {
return Err(CommandFailure::BrokenPipe {
source: err,
status: status.code(),
stderr,
});
}
if status.success() {
Ok(stdout)
} else {
Err(CommandFailure::Exit {
status: status.code(),
stderr,
})
}
}
fn run_child(mut command: Command, input: &[u8]) -> Result<Vec<u8>, CommandFailure> {
let mut child = command.spawn().map_err(CommandFailure::Spawn)?;
- let mut broken_pipe = None;
- if let Some(mut stdin) = child.stdin.take() {
- match stdin.write_all(input) {
- Ok(()) => {}
- Err(err) => {
- if err.kind() == io::ErrorKind::BrokenPipe {
- broken_pipe = Some(err);
- } else {
- return Err(CommandFailure::Io(err));
- }
- }
- }
- }
-
- let mut stdout_reader = spawn_pipe_reader(child.stdout.take());
// Start readers first to avoid pipe back-pressure deadlocks.
let mut stdout_reader = spawn_pipe_reader(child.stdout.take());
let mut stderr_reader = spawn_pipe_reader(child.stderr.take());
// Write stdin on a separate thread so a non-reading child cannot block us.
let stdin_handle = child.stdin.take().map(|mut stdin| {
let buf = input.to_vec();
thread::spawn(move || {
let res = stdin.write_all(&buf);
drop(stdin); // close to signal EOF
res
})
});
let status = match wait_for_exit(&mut child, COMMAND_TIMEOUT) {
Ok(status) => status,
Err(err) => {
- let _ = join_reader(stdout_reader.take());
let _ = join_reader(stdout_reader.take());
let _ = join_reader(stderr_reader.take());
if let Some(handle) = stdin_handle {
let _ = handle.join();
}
return Err(err);
}
};
- let stdout = join_reader(stdout_reader.take()).map_err(CommandFailure::Io)?;
- let stderr = join_reader(stderr_reader.take()).map_err(CommandFailure::Io)?;
-
- if let Some(err) = broken_pipe {
- return Err(CommandFailure::BrokenPipe {
- source: err,
- status: status.code(),
- stderr,
- });
let stdout = join_reader(stdout_reader.take()).map_err(CommandFailure::Io)?;
let stderr = join_reader(stderr_reader.take()).map_err(CommandFailure::Io)?;
if let Some(handle) = stdin_handle {
match handle.join() {
Ok(Ok(())) => {}
Ok(Err(err)) => {
if err.kind() == io::ErrorKind::BrokenPipe {
return Err(CommandFailure::BrokenPipe {
source: err,
status: status.code(),
stderr,
});
}
return Err(CommandFailure::Io(err));
}
Err(_) => return Err(CommandFailure::Io(io::Error::other("stdin writer panicked"))),
}
}
if status.success() {
Ok(stdout)
} else {
- Err(CommandFailure::Exit {
- status: status.code(),
- stderr,
Err(CommandFailure::Exit { status: status.code(), stderr })
}
}
🤖 Prompt for AI Agents
In src/stdlib/command.rs around lines 203 to 250, the current run_child
implementation writes to the child's stdin synchronously before spawning
stdout/stderr readers and before applying the timeout, which can deadlock if the
child fills its stdout/stderr; to fix it, first spawn the stdout and stderr
reader threads (spawn_pipe_reader) and then perform the stdin.write_all on a
dedicated writer thread (or spawn_blocking) that captures any BrokenPipe or IO
error into a shared channel/Result; wait for child exit with wait_for_exit as
before, and on exit/timeout always join the writer thread and both reader
threads (join_reader) to ensure they terminate and return their output/errors;
propagate the writer error as BrokenPipe/Io as appropriate, and ensure reader
joins are mapped to CommandFailure::Io when collecting stdout/stderr.

Comment on lines +252 to +256
fn value_from_bytes(bytes: Vec<u8>) -> Value {
match String::from_utf8(bytes) {
Ok(text) => Value::from(text),
Err(err) => Value::from_bytes(err.into_bytes()),
}
Copy link
Contributor

Choose a reason for hiding this comment

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

🧹 Nitpick | 🔵 Trivial

DRY up byte-to-Value conversion across stdlib

Unify value_from_bytes with network::to_value to avoid duplication and inconsistent behaviour. Extract a shared helper (e.g., stdlib::util::to_value(Vec) -> Value) and reuse here and in network.rs.

🤖 Prompt for AI Agents
In src/stdlib/command.rs around lines 252 to 256, the local value_from_bytes
implementation duplicates logic present in network::to_value; extract a shared
function (e.g., pub(crate) fn to_value(bytes: Vec<u8>) -> Value) into
src/stdlib/util.rs, implement the same String::from_utf8 -> Ok(text) =>
Value::from(text) / Err(err) => Value::from_bytes(err.into_bytes()) behavior
there, export it, then replace the body of value_from_bytes with a call to
util::to_value(bytes) and update network.rs to call the new shared
util::to_value as well (adding the necessary use/imports).

Comment on lines +79 to +123
fn open_cache_dir(path: &str) -> Result<Dir, Error> {
let utf_path = Utf8Path::new(path);
if utf_path.as_str().is_empty() {
return Err(Error::new(
ErrorKind::InvalidOperation,
"cache_dir must not be empty",
));
}

if utf_path.is_absolute() {
let mut root = String::new();
for component in utf_path.components() {
match component {
Utf8Component::Prefix(prefix) => root.push_str(prefix.as_str()),
Utf8Component::RootDir => {
root.push('/');
break;
}
_ => break,
}
}
if root.is_empty() {
root.push('/');
}
let root_path = Utf8PathBuf::from(root);

let root_dir = Dir::open_ambient_dir(&root_path, ambient_authority())
.map_err(|err| io_error("open root cache dir", &root_path, &err))?;
let rel = utf_path.strip_prefix(&root_path).map_err(|_| {
Error::new(
ErrorKind::InvalidOperation,
format!("failed to compute relative path for '{utf_path}'"),
)
})?;
if rel.as_str().is_empty() {
return Ok(root_dir);
}

root_dir
.create_dir_all(rel)
.map_err(|err| io_error("create cache dir", utf_path, &err))?;
return root_dir
.open_dir(rel)
.map_err(|err| io_error("open cache dir", utf_path, &err));
}
Copy link
Contributor

Choose a reason for hiding this comment

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

🧹 Nitpick | 🔵 Trivial

Reject filesystem root as a cache directory

Prevent accidental writes to the filesystem root (e.g., "/" or "C:/") when cache_dir is absolute and equals the root. Fail fast with InvalidOperation to avoid surprising behaviour and permissions issues.

Agent prompt:
Add a guard after computing root_path/rel in open_cache_dir: if rel.as_str().is_empty() then return Err(Error::new(ErrorKind::InvalidOperation, "cache_dir must not be the filesystem root")).
🤖 Prompt for AI Agents
In src/stdlib/network.rs around lines 79 to 123, after computing root_path and
rel for an absolute cache_dir, add a guard that rejects the filesystem root: if
rel.as_str().is_empty() then return Err(Error::new(ErrorKind::InvalidOperation,
"cache_dir must not be the filesystem root")); this prevents treating "/" (or
equivalent on Windows) as a writable cache directory and fails fast with
InvalidOperation instead of proceeding to create/open the root directory.

Comment on lines +56 to +64
pub(crate) fn start_http_server(&mut self, body: String) {
if let Some(host) = self.extract_host_from_stdlib_url() {
let _ = TcpStream::connect(host);
}
self.shutdown_http_server();
let (url, handle) = spawn_http_server(body);
self.stdlib_url = Some(url);
self.http_server = Some(handle);
}
Copy link
Contributor

Choose a reason for hiding this comment

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

🧹 Nitpick | 🔵 Trivial

Centralise server unblock-and-join logic in shutdown_http_server

Avoid duplicating the “connect to host to unblock accept, then join” sequence in both start and Drop. Perform it inside shutdown_http_server, call that in start and Drop.

Apply this diff:

@@
 impl CliWorld {
     pub(crate) fn start_http_server(&mut self, body: String) {
-        if let Some(host) = self.extract_host_from_stdlib_url() {
-            let _ = TcpStream::connect(host);
-        }
-        self.shutdown_http_server();
+        self.shutdown_http_server();
         let (url, handle) = spawn_http_server(body);
         self.stdlib_url = Some(url);
         self.http_server = Some(handle);
     }
 
     pub(crate) fn shutdown_http_server(&mut self) {
-        let Some(handle) = self.http_server.take() else {
-            return;
-        };
-        let _ = handle.join();
-        self.stdlib_url = None;
+        if let Some(host) = self.extract_host_from_stdlib_url() {
+            let _ = TcpStream::connect(host);
+        }
+        if let Some(handle) = self.http_server.take() {
+            let _ = handle.join();
+        }
+        self.stdlib_url = None;
     }
@@
 impl Drop for CliWorld {
     fn drop(&mut self) {
-        if let Some(host) = self.extract_host_from_stdlib_url() {
-            let _ = TcpStream::connect(host);
-        }
-        self.shutdown_http_server();
+        self.shutdown_http_server();
         self.restore_environment();
     }
 }

Also applies to: 66-72, 105-112

🤖 Prompt for AI Agents
In tests/cucumber.rs around lines 56-64 (also apply same change to 66-72 and
105-112), the logic that connects to the server host to unblock accept and then
joins the server is duplicated in start_http_server and Drop; move the
unblock-and-join sequence into shutdown_http_server so it performs: if
stdlib_url/host exists, connect to host to unblock accept, then join the
http_server handle and clear stdlib_url/http_server; update start_http_server to
call shutdown_http_server before spawning the new server (removing the duplicate
connect/join there), and update Drop to call shutdown_http_server instead of
repeating the logic; ensure shutdown_http_server is responsible for safely
handling None values and joining the handle only once.

Comment on lines +64 to +81
#[rstest]
#[case::uppercase(
compile_uppercase_helper,
"cmd_upper",
"shell_upper",
"{{ 'hello' | shell(cmd) | trim }}",
ShellExpectation::Success("HELLO")
)]
#[case::failure(
compile_failure_helper,
"cmd_fail",
"shell_fail",
"{{ 'data' | shell(cmd) }}",
ShellExpectation::Failure {
substrings: &["command", "exited"],
},
)]
fn shell_filter_behaviour(
Copy link
Contributor

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟡 Minor

Broaden failure assertions to tolerate different failure modes

Do not force “exited” specifically; IO failures (e.g., Broken pipe) and signal terminations are also valid. Accept any canonical failure phrasing emitted by command_error.

Apply this diff:

-    ShellExpectation::Failure {
-        substrings: &["command", "exited"],
-    },
+    ShellExpectation::Failure {
+        substrings: &["command", "failed"]
+    },
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
#[rstest]
#[case::uppercase(
compile_uppercase_helper,
"cmd_upper",
"shell_upper",
"{{ 'hello' | shell(cmd) | trim }}",
ShellExpectation::Success("HELLO")
)]
#[case::failure(
compile_failure_helper,
"cmd_fail",
"shell_fail",
"{{ 'data' | shell(cmd) }}",
ShellExpectation::Failure {
substrings: &["command", "exited"],
},
)]
fn shell_filter_behaviour(
#[rstest]
#[case::uppercase(
compile_uppercase_helper,
"cmd_upper",
"shell_upper",
"{{ 'hello' | shell(cmd) | trim }}",
ShellExpectation::Success("HELLO")
)]
#[case::failure(
compile_failure_helper,
"cmd_fail",
"shell_fail",
"{{ 'data' | shell(cmd) }}",
ShellExpectation::Failure {
substrings: &["command", "failed"]
},
)]
fn shell_filter_behaviour(
compile_helper: fn(&str) -> Utf8PathBuf,
cmd_name: &str,
shell_name: &str,
template_str: &str,
expectation: ShellExpectation,
) {
// …
}
🤖 Prompt for AI Agents
tests/std_filter_tests/command_filters.rs around lines 64 to 81: the failure
case currently asserts the error contains the specific word "exited", which is
too strict; change the test to accept any canonical failure phrasing from
command_error by removing the strict "exited" requirement—either assert only
that "command" appears in the message or replace the substrings array with a
broader set of possible failure tokens (e.g., "exited", "failed", "broken pipe",
"killed", "signal") so the test passes for IO failures, signal terminations, or
exit-status failures.

@@ -0,0 +1,107 @@
use std::{
Copy link
Contributor

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟡 Minor

Add a module-level doc comment.

Document the module purpose per guidelines.

Apply:

+//! Tests for stdlib network helpers: fetch success, cache behaviour, and error/impurity.
 use std::{
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
use std::{
//! Tests for stdlib network helpers: fetch success, cache behaviour, and error/impurity.
use std::{
🤖 Prompt for AI Agents
In tests/std_filter_tests/network_functions.rs at line 1, add a module-level doc
comment at the top of the file that succinctly states the module purpose per
project guidelines; insert a triple-slash (///) or module-level block comment
(/*! ... */) before the first use statement with a one-line summary of what the
tests in this module cover, optionally followed by a brief sentence about key
behaviors tested or relevant configuration, and ensure the comment follows the
same style/formatting used across the codebase.

Comment on lines +55 to +58
let temp = tempdir().expect("tempdir");
let cache_dir = temp.path().join("cache");
let cache_str = cache_dir.to_str().expect("utf8 cache dir").to_owned();
let (url, handle) = start_server("cached");
Copy link
Contributor

Choose a reason for hiding this comment

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

🛠️ Refactor suggestion | 🟠 Major

Use camino for UTF‑8 paths; avoid to_str on std::path.

Avoid lossy conversions; build the cache dir with Utf8PathBuf and take .as_str().

Apply:

-    let temp = tempdir().expect("tempdir");
-    let cache_dir = temp.path().join("cache");
-    let cache_str = cache_dir.to_str().expect("utf8 cache dir").to_owned();
+    let temp = tempdir().expect("tempdir");
+    let cache_root = camino::Utf8PathBuf::from_path_buf(temp.path().to_path_buf())
+        .expect("utf8 temp path");
+    let cache_dir = cache_root.join("cache");
+    let cache_str = cache_dir.as_str().to_owned();
     let (url, handle) = start_server("cached");

Also add the import outside this hunk:

use camino::Utf8PathBuf; // or qualify as shown above

As per coding guidelines

🤖 Prompt for AI Agents
In tests/std_filter_tests/network_functions.rs around lines 55–58, the test is
using std::path::Path::to_str which can be lossy; replace that logic by
constructing the cache directory as a camino::Utf8PathBuf (join "cache" on an
Utf8PathBuf) and use its .as_str() for the UTF‑8 string, avoiding
to_str/to_owned on std::path; also add the import use camino::Utf8PathBuf near
the top of the file.

Comment on lines +61 to +64
env.add_template(
"fetch_cache",
"{{ fetch(url, cache=True, cache_dir=cache_dir) }}",
)
Copy link
Contributor

Choose a reason for hiding this comment

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

🛠️ Refactor suggestion | 🟠 Major

🧩 Analysis chain

Verify MiniJinja boolean literal casing.

Jinja/MiniJinja booleans are typically lower‑case (true/false). Ensure capitalised True parses, or switch to lower‑case.

Apply if needed:

-        "{{ fetch(url, cache=True, cache_dir=cache_dir) }}",
+        "{{ fetch(url, cache=true, cache_dir=cache_dir) }}",

Run to confirm parsing:


🏁 Script executed:

#!/bin/bash
# Show all templates using cache=True to fix casing if required
rg -nP --type=rust -C2 'cache\s*=\s*True'

Length of output: 371


Does MiniJinja accept capitalised boolean literals (True/False), or are they required to be lower-case (true/false) per Jinja spec?

Lower-case boolean literal in MiniJinja template Change cache=True to cache=true in tests/std_filter_tests/network_functions.rs:63.

🤖 Prompt for AI Agents
In tests/std_filter_tests/network_functions.rs around lines 61 to 64, the
MiniJinja template uses an uppercase Python-style boolean literal `cache=True`;
change it to the MiniJinja lowercase boolean `cache=true` in the
env.add_template call so the template parses and evaluates correctly.

Comment on lines +202 to +216
// The reviewer requested nested optional checks to keep the host parsing explicit
// for readability, so silence the lint that prefers collapsing them.
#[allow(
clippy::collapsible_if,
reason = "Review requested explicit nested host extraction checks"
)]
#[given(regex = r#"^an HTTP server returning "(.+)"$"#)]
fn http_server_returning(world: &mut CliWorld, body: String) {
if let Some(url) = world.stdlib_url.as_ref() {
if let Some(host) = extract_host_from_url(url) {
let _ = TcpStream::connect(host);
}
}
world.start_http_server(body);
}
Copy link
Contributor

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟠 Major

Remove forbidden #[allow] and eliminate the lint via code.

Do not use #[allow]; refactor to avoid clippy::collapsible_if.

Apply:

-// The reviewer requested nested optional checks to keep the host parsing explicit
-// for readability, so silence the lint that prefers collapsing them.
-#[allow(
-    clippy::collapsible_if,
-    reason = "Review requested explicit nested host extraction checks"
-)]
 #[given(regex = r#"^an HTTP server returning "(.+)"$"#)]
 fn http_server_returning(world: &mut CliWorld, body: String) {
-    if let Some(url) = world.stdlib_url.as_ref() {
-        if let Some(host) = extract_host_from_url(url) {
-            let _ = TcpStream::connect(host);
-        }
-    }
+    if let Some(host) = world
+        .stdlib_url
+        .as_deref()
+        .and_then(extract_host_from_url)
+    {
+        let _ = TcpStream::connect(host);
+    }
     world.start_http_server(body);
 }

If you must justify a lint expectation elsewhere, prefer:

#[expect(clippy::..., reason = "why it is safe/necessary")]

As per coding guidelines

🤖 Prompt for AI Agents
In tests/steps/stdlib_steps.rs around lines 202 to 216, remove the forbidden
#[allow(...)] and replace the nested if-let blocks with a single let-chain
conditional so the nested host extraction remains explicit but
clippy::collapsible_if is no longer triggered; specifically, remove the
attribute and change the two nested if-let statements into one if-let chain that
checks world.stdlib_url.as_ref() and then extract_host_from_url(url) in the same
conditional, keeping the TcpStream::connect(host) call and the subsequent
world.start_http_server(body) call unchanged.

Copy link
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: 35

📜 Review details

Configuration used: CodeRabbit UI

Review profile: ASSERTIVE

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between 2addbee and 7178137.

📒 Files selected for processing (31)
  • .github/actions/stage/action.yml (1 hunks)
  • .github/actions/stage/scripts/stage.py (1 hunks)
  • .github/actions/stage/scripts/stage_common/__init__.py (1 hunks)
  • .github/actions/stage/scripts/stage_common/checksum_utils.py (1 hunks)
  • .github/actions/stage/scripts/stage_common/config.py (1 hunks)
  • .github/actions/stage/scripts/stage_common/environment.py (1 hunks)
  • .github/actions/stage/scripts/stage_common/errors.py (1 hunks)
  • .github/actions/stage/scripts/stage_common/fs_utils.py (1 hunks)
  • .github/actions/stage/scripts/stage_common/github_output.py (1 hunks)
  • .github/actions/stage/scripts/stage_common/glob_utils.py (1 hunks)
  • .github/actions/stage/scripts/stage_common/staging.py (1 hunks)
  • .github/actions/stage/scripts/stage_common/template_utils.py (1 hunks)
  • .github/release-staging.toml (1 hunks)
  • .github/workflows/release.yml (4 hunks)
  • .github/workflows/scripts/normalise_windows_paths.py (1 hunks)
  • .github/workflows/scripts/stage_common.py (0 hunks)
  • .github/workflows/scripts/stage_macos.py (0 hunks)
  • .github/workflows/scripts/stage_windows.py (0 hunks)
  • codex/refactor-python-staging-scripts-into-github-action (1 hunks)
  • cyclopts/__init__.pyi (1 hunks)
  • docs/netsuke-design.md (3 hunks)
  • scripts/_release_upload_deps.py (1 hunks)
  • scripts/upload_release_assets.py (6 hunks)
  • tests_python/conftest.py (1 hunks)
  • tests_python/stage_test_helpers.py (1 hunks)
  • tests_python/test_stage_action.py (1 hunks)
  • tests_python/test_stage_cli.py (1 hunks)
  • tests_python/test_stage_common.py (0 hunks)
  • tests_python/test_stage_common_config.py (1 hunks)
  • tests_python/test_stage_common_module.py (1 hunks)
  • tests_python/test_stage_common_staging.py (1 hunks)
💤 Files with no reviewable changes (4)
  • .github/workflows/scripts/stage_common.py
  • tests_python/test_stage_common.py
  • .github/workflows/scripts/stage_windows.py
  • .github/workflows/scripts/stage_macos.py
🧰 Additional context used
📓 Path-based instructions (4)
**/*.py

⚙️ CodeRabbit configuration file

**/*.py: - Keep cyclomatic complexity ≤ 12

  • Follow single responsibility and CQRS (command/query segregation)
  • 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 elements.

Files:

  • tests_python/conftest.py
  • scripts/_release_upload_deps.py
  • tests_python/stage_test_helpers.py
  • tests_python/test_stage_common_module.py
  • tests_python/test_stage_common_config.py
  • tests_python/test_stage_cli.py
  • tests_python/test_stage_action.py
  • tests_python/test_stage_common_staging.py
  • scripts/upload_release_assets.py
docs/**/*.md

📄 CodeRabbit inference engine (AGENTS.md)

docs/**/*.md: Use docs/ markdown as the reference for requirements, dependencies, and architectural decisions
Proactively update docs/ when decisions, requirements, or dependencies change
Documentation must use en-GB-oxendict spelling and grammar (LICENSE name unchanged)

Files:

  • docs/netsuke-design.md
**/*.md

📄 CodeRabbit inference engine (AGENTS.md)

**/*.md: Validate Markdown with make markdownlint
Run make fmt after doc changes to format Markdown and fix tables
Validate Mermaid diagrams in Markdown using make nixie
Wrap paragraphs and bullet points at 80 columns
Wrap code blocks at 120 columns
Do not wrap tables and headings
Use dashes (-) for list bullets
Use GitHub-flavoured Markdown footnotes ([^1]) for references and footnotes

Files:

  • docs/netsuke-design.md

⚙️ CodeRabbit configuration file

**/*.md: * Avoid 2nd person or 1st person pronouns ("I", "you", "we")

  • Use en-GB-oxendict (-ize / -yse / -our) spelling and grammar
  • Headings must not be wrapped.
  • Documents must start with a level 1 heading
  • Headings must correctly increase or decrease by no more than one level at a time
  • Use GitHub-flavoured Markdown style for footnotes and endnotes.
  • Numbered footnotes must be numbered by order of appearance in the document.

Files:

  • docs/netsuke-design.md
docs/**/*.{rs,md}

📄 CodeRabbit inference engine (docs/rust-doctest-dry-guide.md)

docs/**/*.{rs,md}: In fenced documentation code blocks, specify the language as rust (```rust)
Use assert!, assert_eq!, and assert_ne! in doctests to verify outcomes rather than relying on panic-free execution alone
Avoid using .unwrap() or .expect() in examples; handle errors idiomatically
For fallible examples, write an explicit fn main() -> Result<...> and hide boilerplate with # lines
Alternatively, use the (()) shorthand at the end of a code block (no whitespace) to create an implicit Result-returning main
Use hidden lines (#) to hide setup, main wrappers, and non-essential use statements in doctests
Prefer no_run over ignore for examples with side effects (e.g., network, filesystem)
Use should_panic for examples intended to panic; the test should fail if it does not panic
Use compile_fail for examples that should not compile, but recognize its brittleness across compiler versions
Use edition20xx (e.g., edition2018, edition2021) on code fences when demonstrating edition-specific syntax
Avoid relying solely on #[cfg(feature = "...")] inside doctest code blocks, which can yield misleading empty-ok tests when the feature is disabled

Files:

  • docs/netsuke-design.md
🧬 Code graph analysis (18)
tests_python/conftest.py (1)
tests_python/test_stage_cli.py (1)
  • workspace (36-41)
.github/workflows/scripts/normalise_windows_paths.py (1)
.github/actions/stage/scripts/stage.py (1)
  • main (34-57)
.github/actions/stage/scripts/stage_common/environment.py (1)
.github/actions/stage/scripts/stage_common/errors.py (1)
  • StageError (4-5)
.github/actions/stage/scripts/stage_common/__init__.py (4)
.github/actions/stage/scripts/stage_common/config.py (3)
  • ArtefactConfig (38-81)
  • StagingConfig (85-193)
  • load_config (196-251)
.github/actions/stage/scripts/stage_common/environment.py (1)
  • require_env_path (13-30)
.github/actions/stage/scripts/stage_common/errors.py (1)
  • StageError (4-5)
.github/actions/stage/scripts/stage_common/staging.py (2)
  • StageResult (58-64)
  • stage_artefacts (119-214)
.github/actions/stage/scripts/stage_common/fs_utils.py (2)
.github/actions/stage/scripts/stage_common/errors.py (1)
  • StageError (4-5)
.github/actions/stage/scripts/stage_common/config.py (1)
  • staging_dir (167-169)
scripts/_release_upload_deps.py (2)
cyclopts/__init__.pyi (3)
  • App (11-14)
  • Parameter (8-9)
  • default (13-13)
scripts/upload_release_assets.py (1)
  • main (224-276)
cyclopts/__init__.pyi (1)
tests_python/test_stage_cli.py (1)
  • default (25-27)
tests_python/test_stage_common_module.py (3)
tests_python/conftest.py (2)
  • stage_common (16-24)
  • workspace (28-34)
.github/actions/stage/scripts/stage_common/errors.py (1)
  • StageError (4-5)
.github/actions/stage/scripts/stage_common/environment.py (1)
  • require_env_path (13-30)
tests_python/test_stage_common_config.py (3)
tests_python/conftest.py (2)
  • stage_common (16-24)
  • workspace (28-34)
.github/actions/stage/scripts/stage_common/config.py (4)
  • StagingConfig (85-193)
  • as_template_context (176-193)
  • load_config (196-251)
  • staging_dir (167-169)
.github/actions/stage/scripts/stage_common/errors.py (1)
  • StageError (4-5)
.github/actions/stage/scripts/stage.py (6)
tests_python/conftest.py (2)
  • stage_common (16-24)
  • workspace (28-34)
.github/actions/stage/scripts/stage_common/errors.py (1)
  • StageError (4-5)
.github/actions/stage/scripts/stage_common/config.py (2)
  • load_config (196-251)
  • staging_dir (167-169)
.github/actions/stage/scripts/stage_common/environment.py (1)
  • require_env_path (13-30)
.github/actions/stage/scripts/stage_common/staging.py (1)
  • stage_artefacts (119-214)
cyclopts/__init__.pyi (2)
  • App (11-14)
  • default (13-13)
tests_python/test_stage_cli.py (2)
tests_python/stage_test_helpers.py (2)
  • decode_output_file (10-37)
  • write_workspace_inputs (40-52)
.github/actions/stage/scripts/stage.py (1)
  • main (34-57)
.github/actions/stage/scripts/stage_common/glob_utils.py (1)
tests_python/conftest.py (1)
  • workspace (28-34)
.github/actions/stage/scripts/stage_common/staging.py (7)
.github/actions/stage/scripts/stage_common/checksum_utils.py (1)
  • write_checksum (11-21)
.github/actions/stage/scripts/stage_common/config.py (4)
  • ArtefactConfig (38-81)
  • StagingConfig (85-193)
  • staging_dir (167-169)
  • as_template_context (176-193)
.github/actions/stage/scripts/stage_common/errors.py (1)
  • StageError (4-5)
.github/actions/stage/scripts/stage_common/fs_utils.py (1)
  • safe_destination_path (12-21)
.github/actions/stage/scripts/stage_common/github_output.py (1)
  • write_github_output (12-26)
.github/actions/stage/scripts/stage_common/glob_utils.py (2)
  • glob_root_and_pattern (38-51)
  • match_candidate_path (10-35)
.github/actions/stage/scripts/stage_common/template_utils.py (2)
  • render_template (30-37)
  • resolve_artefact_source (40-52)
tests_python/test_stage_action.py (1)
tests_python/conftest.py (1)
  • stage_common (16-24)
.github/actions/stage/scripts/stage_common/config.py (3)
.github/actions/stage/scripts/stage_common/environment.py (1)
  • require_env_path (13-30)
.github/actions/stage/scripts/stage_common/errors.py (1)
  • StageError (4-5)
tests_python/conftest.py (1)
  • workspace (28-34)
.github/actions/stage/scripts/stage_common/template_utils.py (3)
.github/actions/stage/scripts/stage_common/errors.py (1)
  • StageError (4-5)
.github/actions/stage/scripts/stage_common/glob_utils.py (1)
  • match_candidate_path (10-35)
.github/actions/stage/scripts/stage_common/config.py (1)
  • ArtefactConfig (38-81)
tests_python/test_stage_common_staging.py (5)
tests_python/stage_test_helpers.py (2)
  • decode_output_file (10-37)
  • write_workspace_inputs (40-52)
tests_python/conftest.py (2)
  • stage_common (16-24)
  • workspace (28-34)
.github/actions/stage/scripts/stage_common/config.py (3)
  • StagingConfig (85-193)
  • ArtefactConfig (38-81)
  • staging_dir (167-169)
.github/actions/stage/scripts/stage_common/staging.py (1)
  • stage_artefacts (119-214)
.github/actions/stage/scripts/stage_common/errors.py (1)
  • StageError (4-5)
scripts/upload_release_assets.py (1)
scripts/_release_upload_deps.py (3)
  • load_cyclopts (37-59)
  • load_plumbum (62-93)
  • run_cli (96-142)
🪛 Ruff (0.13.3)
.github/actions/stage/scripts/stage_common/__init__.py

8-17: __all__ is not sorted

Apply an isort-style sorting to __all__

(RUF022)

scripts/_release_upload_deps.py

9-9: Import from collections.abc instead: Sequence

Import from collections.abc

(UP035)


43-43: Local variable exc is assigned to but never used

Remove assignment to unused variable exc

(F841)


48-48: Undefined name exc

(F821)


56-56: Boolean positional value in function call

(FBT003)


59-59: Boolean positional value in function call

(FBT003)


72-72: Local variable exc is assigned to but never used

Remove assignment to unused variable exc

(F841)


79-79: Undefined name exc

(F821)

.github/actions/stage/scripts/stage_common/staging.py

45-51: __all__ is not sorted

Apply an isort-style sorting to __all__

(RUF022)

.github/actions/stage/scripts/stage_common/template_utils.py

41-41: Remove quotes from type annotation

Remove quotes

(UP037)

tests_python/test_stage_common_staging.py

203-203: Unused function argument: stage_common

(ARG001)


361-361: Unused function argument: stage_common

(ARG001)


372-372: Unused noqa directive (unused: ARG001)

Remove unused noqa directive

(RUF100)


373-373: Unused function argument: stage_common

(ARG001)


386-386: Unused noqa directive (unused: ARG001)

Remove unused noqa directive

(RUF100)


387-387: Unused function argument: stage_common

(ARG001)


395-395: Probable insecure usage of temporary file or directory: "/tmp/dist/*.zip"

(S108)


400-400: Unused noqa directive (unused: ARG001)

Remove unused noqa directive

(RUF100)


401-401: Unused function argument: stage_common

(ARG001)

🔍 Remote MCP

Here is a concise summary of additional context relevant for reviewing PR #187:

  1. ureq crate (v2.10.5) provides a synchronous HTTP client supporting GET with timeout configuration via ureq::Agent::new().timeout_read()… ([Ref::Ref_search_documentation—failed to fetch docs]).
  2. wait-timeout crate (v0.2) supplies a WaitTimeout trait on std::process::Child, enabling child.wait_timeout(Duration) to implement timeouts for external commands ([Ref::Ref_search_documentation—failed to fetch docs]).
  3. In tests/cucumber.rs, the suggested refactoring matches existing helper signatures: fn extract_host_from_stdlib_url(&self) -> Option<&str> is consistent with fn extract_host_from_url(url: &str) -> Option<&str> patterns found in tests/steps/stdlib_steps.rs ([Deepwiki::read_wiki_contents]).
  4. CommandFixture in tests/std_filter_tests/command_filters.rs already encapsulates process spawning and environment, so extending it with a Windows-only with_path_prefix() aligns with its existing builder-like API ([Deepwiki::read_wiki_contents]).
⏰ 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: release / build-macos (aarch64-apple-darwin, macos-14, macos-arm64, arm64, macos-aarch64)
  • GitHub Check: release / build-macos (x86_64-apple-darwin, macos-13, macos-x86_64, x86_64, macos-x86_64)
  • GitHub Check: release / build-windows (x86_64-pc-windows-msvc, amd64, x64, windows-x86_64)
  • GitHub Check: release / build-linux (x86_64-unknown-linux-gnu, linux-amd64, amd64)
  • GitHub Check: release / build-windows (aarch64-pc-windows-msvc, arm64, arm64, windows-aarch64)
  • GitHub Check: build-test (ubuntu-latest, stable)
🔇 Additional comments (40)
docs/netsuke-design.md (1)

925-939: Record the clarified impurity contract.

Document how fetch, shell, and grep flip StdlibState and note the SHA-256 cache key so readers understand the caching boundaries.

cyclopts/__init__.pyi (1)

13-13: Align stub signature with implementation
Verify the runtime signature of App.default in cyclopts/init.py and update the stub at line 13 to match—use Callable[P, T] if preserved or Callable[..., object] otherwise.

.github/actions/stage/scripts/stage_common/errors.py (1)

1-5: LGTM!

The error class is appropriately defined with a clear docstring.

.github/actions/stage/scripts/stage_common/environment.py (1)

13-30: LGTM!

The function correctly validates environment variables and follows numpy-style docstring conventions with Parameters and Raises sections.

.github/release-staging.toml (7)

1-10: LGTM!

The common configuration section correctly defines the staging structure, artefact sources, and fallback paths for man page discovery.


12-16: LGTM!

Windows x86_64 target configuration is correct.


18-22: LGTM!

Windows aarch64 target configuration is correct.


24-27: LGTM!

macOS x86_64 target configuration is correct.


29-32: LGTM!

macOS aarch64 target configuration is correct.


34-37: LGTM!

Linux x86_64 target configuration is correct.


39-42: LGTM!

Linux aarch64 target configuration is correct.

.github/actions/stage/scripts/stage.py (3)

8-19: LGTM!

The module docstring appropriately explains the CLI entry point's purpose and provides a clear usage example.


21-30: LGTM!

Imports and application setup are correct. The help text uses proper en-GB spelling.


60-61: LGTM!

The __main__ guard correctly invokes the CLI application.

.github/actions/stage/scripts/stage_common/fs_utils.py (1)

1-9: LGTM!

Module setup is correct. The docstring appropriately describes the module's purpose.

tests_python/test_stage_common_config.py (6)

1-9: LGTM!

Module setup is correct.


12-34: LGTM!

The test correctly verifies template context exposure.


36-70: LGTM!

The test correctly verifies configuration merging behaviour.


72-87: LGTM!

The test validates the repository's actual configuration file, ensuring it parses correctly.


89-115: LGTM!

The test correctly verifies environment variable requirement.


117-223: LGTM!

The parametrised test comprehensively covers configuration validation errors with appropriate assertions for error messages and context markers.

.github/workflows/release.yml (5)

175-179: LGTM!

The target_key additions correctly identify Windows targets for the new staging system.


199-202: LGTM!

The workflow correctly invokes the new staging action, replacing inline Bash logic.


207-210: LGTM!

The path normalisation step correctly consumes the artefact_map output from the staging action. Spelling is correct (en-GB).


249-254: LGTM!

The target_key additions correctly identify macOS targets for the new staging system.


275-278: LGTM!

The workflow correctly invokes the new staging action for macOS targets.

.github/actions/stage/action.yml (3)

1-10: LGTM!

Action metadata and input definitions are correct. Spelling follows en-GB conventions.


12-36: LGTM!

Output definitions are comprehensive. The descriptions correctly use en-GB spelling ("artefact", "licence"), whilst the output identifiers follow programmatic naming conventions.


38-59: LGTM!

The run steps correctly install uv, verify its availability, and execute the staging script. The use of commit SHA for the external action enhances security.

.github/actions/stage/scripts/stage_common/__init__.py (1)

1-6: LGTM!

Module docstring and imports correctly establish the public API surface.

tests_python/stage_test_helpers.py (1)

1-7: LGTM!

Module setup is correct.

scripts/_release_upload_deps.py (1)

96-109: Verify Cyclopts App.__call__ accepts a positional arguments sequence.

return support.app(arguments) assumes the App is called with argv as the first positional parameter. Confirm the 2.x API matches this usage.

Check whether Cyclopts v2.x `App.__call__` accepts an optional `argv: Sequence[str] | None` positional argument.
scripts/upload_release_assets.py (6)

48-56: Good abstraction: decouple backends behind support objects.

Map Cyclopts/Plumbum into local aliases cleanly; keeps the module importable without optional deps.


57-61: Type-checking guard for BoundCommand is correct.

Expose the runtime fallback via the support object while preserving static types.


97-102: Efficient and readable candidate discovery.

Yield a sorted iterator with precise filtering; annotation with Iterator[Path] is apt.


168-175: Summariser accepts Iterable and avoids materialising unnecessarily.

Keep memory usage low and API flexible.


178-179: Broaden assets to Iterable without changing behaviour.

Maintain compatibility while enabling streaming sources.


279-306: Route CLI through run_cli for optional-dep resilience.

Delegate parsing to Cyclopts when available, else fall back to argparse with env overrides; this aligns with the new loader.

codex/refactor-python-staging-scripts-into-github-action (2)

1-2: LGTM: Strict error handling configured correctly.

The shebang and bash options enforce robust error handling, appropriate for a quality gate script.


7-7: Verify redundancy of path specification
Both --extra-search-path .github/actions/stage/scripts/ and .github/actions/stage/scripts/*.py point to the same directory; confirm if ty check auto-discovers files via the search path and drop the explicit glob if redundant.

Comment on lines +8 to +17
__all__ = [
"ArtefactConfig",
"RESERVED_OUTPUT_KEYS",
"StageError",
"StageResult",
"StagingConfig",
"load_config",
"require_env_path",
"stage_artefacts",
]
Copy link
Contributor

Choose a reason for hiding this comment

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

🧹 Nitpick | 🔵 Trivial

Sort __all__ alphabetically.

Static analysis indicates that __all__ is not sorted. Whilst functional, alphabetical ordering improves maintainability.

As per coding guidelines:

Apply this diff:

 __all__ = [
     "ArtefactConfig",
+    "load_config",
     "RESERVED_OUTPUT_KEYS",
+    "require_env_path",
+    "stage_artefacts",
     "StageError",
     "StageResult",
     "StagingConfig",
-    "load_config",
-    "require_env_path",
-    "stage_artefacts",
 ]

Committable suggestion skipped: line range outside the PR's diff.

🧰 Tools
🪛 Ruff (0.13.3)

8-17: __all__ is not sorted

Apply an isort-style sorting to __all__

(RUF022)

🤖 Prompt for AI Agents
In .github/actions/stage/scripts/stage_common/__init__.py around lines 8 to 17,
the __all__ export list is not alphabetically ordered; reorder the identifiers
in that list into ascending alphabetical order (by name) and update the list
in-place so entries like ArtefactConfig, RESERVED_OUTPUT_KEYS, StageError,
StageResult, StagingConfig, load_config, require_env_path, stage_artefacts are
sorted lexicographically.

Comment on lines +11 to +21
def write_checksum(path: Path, algorithm: str) -> str:
"""Write the checksum sidecar for ``path`` using ``algorithm``."""

hasher = hashlib.new(algorithm)
with path.open("rb") as handle:
for chunk in iter(lambda: handle.read(8192), b""):
hasher.update(chunk)
digest = hasher.hexdigest()
checksum_path = path.with_name(f"{path.name}.{algorithm}")
checksum_path.write_text(f"{digest} {path.name}\n", encoding="utf-8")
return digest
Copy link
Contributor

Choose a reason for hiding this comment

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

🛠️ Refactor suggestion | 🟠 Major

Expand docstring with structured sections.

Public functions must follow numpy-style docstrings including Parameters and Returns sections.

Apply this diff:

 def write_checksum(path: Path, algorithm: str) -> str:
-    """Write the checksum sidecar for ``path`` using ``algorithm``."""
+    """Write the checksum sidecar for ``path`` using ``algorithm``.
+
+    Parameters
+    ----------
+    path:
+        Path to the file for which the checksum will be computed.
+    algorithm:
+        Hashing algorithm name supported by :mod:`hashlib` (e.g., ``"sha256"``).
+
+    Returns
+    -------
+    str
+        Hexadecimal digest of the computed checksum.
+    """

As per coding guidelines.

📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
def write_checksum(path: Path, algorithm: str) -> str:
"""Write the checksum sidecar for ``path`` using ``algorithm``."""
hasher = hashlib.new(algorithm)
with path.open("rb") as handle:
for chunk in iter(lambda: handle.read(8192), b""):
hasher.update(chunk)
digest = hasher.hexdigest()
checksum_path = path.with_name(f"{path.name}.{algorithm}")
checksum_path.write_text(f"{digest} {path.name}\n", encoding="utf-8")
return digest
def write_checksum(path: Path, algorithm: str) -> str:
"""Write the checksum sidecar for ``path`` using ``algorithm``.
Parameters
----------
path:
Path to the file for which the checksum will be computed.
algorithm:
Hashing algorithm name supported by :mod:`hashlib` (e.g., ``"sha256"``).
Returns
-------
str
Hexadecimal digest of the computed checksum.
"""
hasher = hashlib.new(algorithm)
with path.open("rb") as handle:
for chunk in iter(lambda: handle.read(8192), b""):
hasher.update(chunk)
digest = hasher.hexdigest()
checksum_path = path.with_name(f"{path.name}.{algorithm}")
checksum_path.write_text(f"{digest} {path.name}\n", encoding="utf-8")
return digest
🤖 Prompt for AI Agents
In .github/actions/stage/scripts/stage_common/checksum_utils.py around lines 11
to 21, the write_checksum function's docstring is currently a single-line
description; update it to a numpy-style docstring that includes a short summary,
a Parameters section documenting 'path' (Path) and 'algorithm' (str) with
descriptions, and a Returns section describing the returned str digest; keep
existing one-line summary, add types and brief explanations for each parameter,
and document that the function writes a sidecar file named
"<filename>.<algorithm>" containing the checksum and filename before returning
the hex digest.

Comment on lines +297 to +316
for index, entry in enumerate(entries, start=1):
source = entry.get("source")
if not isinstance(source, str) or not source:
message = (
"Missing required artefact key 'source' "
f"in entry #{index} of {config_path}"
)
raise StageError(message)
alternatives = _normalise_alternatives(
entry.get("alternatives", []), index, config_path
)
artefacts.append(
ArtefactConfig(
source=source,
required=entry.get("required", True),
output=entry.get("output"),
destination=entry.get("destination"),
alternatives=alternatives,
)
)
Copy link
Contributor

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟠 Major

Validate artefact entries before accessing keys.

Fix the loop to reject non-mapping artefact entries with a StageError; the current version will explode with AttributeError when the TOML contains an incorrect array item, bypassing the intended configuration error reporting.

     artefacts: list[ArtefactConfig] = []
     for index, entry in enumerate(entries, start=1):
+        if not isinstance(entry, dict):
+            message = (
+                "Artefact entries must be tables of key/value pairs "
+                f"(entry #{index} in {config_path})"
+            )
+            raise StageError(message)
         source = entry.get("source")
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
for index, entry in enumerate(entries, start=1):
source = entry.get("source")
if not isinstance(source, str) or not source:
message = (
"Missing required artefact key 'source' "
f"in entry #{index} of {config_path}"
)
raise StageError(message)
alternatives = _normalise_alternatives(
entry.get("alternatives", []), index, config_path
)
artefacts.append(
ArtefactConfig(
source=source,
required=entry.get("required", True),
output=entry.get("output"),
destination=entry.get("destination"),
alternatives=alternatives,
)
)
artefacts: list[ArtefactConfig] = []
for index, entry in enumerate(entries, start=1):
if not isinstance(entry, dict):
message = (
"Artefact entries must be tables of key/value pairs "
f"(entry #{index} in {config_path})"
)
raise StageError(message)
source = entry.get("source")
if not isinstance(source, str) or not source:
message = (
"Missing required artefact key 'source' "
f"in entry #{index} of {config_path}"
)
raise StageError(message)
alternatives = _normalise_alternatives(
entry.get("alternatives", []), index, config_path
)
artefacts.append(
ArtefactConfig(
source=source,
required=entry.get("required", True),
output=entry.get("output"),
destination=entry.get("destination"),
alternatives=alternatives,
)
)
🤖 Prompt for AI Agents
In .github/actions/stage/scripts/stage_common/config.py around lines 297 to 316,
the loop assumes each entry is a mapping and calls entry.get(...), which will
raise AttributeError for non-mapping TOML items; update the loop to validate
each entry is a mapping (e.g., isinstance(entry, dict)) before accessing keys
and raise a StageError with a clear message including the entry index and
config_path when it is not; then proceed to call entry.get(...) only after the
type check so malformed array items produce a controlled StageError instead of
an AttributeError.

Comment on lines +12 to +21
def safe_destination_path(staging_dir: Path, destination: str) -> Path:
"""Return ``destination`` resolved beneath ``staging_dir``."""

target = (staging_dir / destination).resolve()
staging_root = staging_dir.resolve()
if not target.is_relative_to(staging_root):
message = f"Destination escapes staging directory: {destination}"
raise StageError(message)
target.parent.mkdir(parents=True, exist_ok=True)
return target
Copy link
Contributor

Choose a reason for hiding this comment

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

🛠️ Refactor suggestion | 🟠 Major

Expand the docstring to full numpy-style format.

This function is public (listed in __all__) and implements path traversal protection, but the docstring lacks Parameters, Returns, and Raises sections required by coding guidelines.

Apply this diff:

 def safe_destination_path(staging_dir: Path, destination: str) -> Path:
-    """Return ``destination`` resolved beneath ``staging_dir``."""
+    """Return ``destination`` resolved beneath ``staging_dir``.
+
+    Parameters
+    ----------
+    staging_dir : Path
+        Base directory under which the destination must reside.
+    destination : str
+        Relative path to resolve beneath ``staging_dir``.
+
+    Returns
+    -------
+    Path
+        Absolute resolved path with parent directories created.
+
+    Raises
+    ------
+    StageError
+        Raised when ``destination`` attempts to escape ``staging_dir`` via path
+        traversal (e.g. using ``..``).
+    """
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
def safe_destination_path(staging_dir: Path, destination: str) -> Path:
"""Return ``destination`` resolved beneath ``staging_dir``."""
target = (staging_dir / destination).resolve()
staging_root = staging_dir.resolve()
if not target.is_relative_to(staging_root):
message = f"Destination escapes staging directory: {destination}"
raise StageError(message)
target.parent.mkdir(parents=True, exist_ok=True)
return target
def safe_destination_path(staging_dir: Path, destination: str) -> Path:
"""Return ``destination`` resolved beneath ``staging_dir``.
Parameters
----------
staging_dir : Path
Base directory under which the destination must reside.
destination : str
Relative path to resolve beneath ``staging_dir``.
Returns
-------
Path
Absolute resolved path with parent directories created.
Raises
------
StageError
Raised when ``destination`` attempts to escape ``staging_dir`` via path
traversal (e.g. using ``..``).
"""
target = (staging_dir / destination).resolve()
staging_root = staging_dir.resolve()
if not target.is_relative_to(staging_root):
message = f"Destination escapes staging directory: {destination}"
raise StageError(message)
target.parent.mkdir(parents=True, exist_ok=True)
return target
🤖 Prompt for AI Agents
In .github/actions/stage/scripts/stage_common/fs_utils.py around lines 12 to 21,
the public function safe_destination_path lacks a full numpy-style docstring;
update it to include a short description plus "Parameters" (staging_dir: Path —
root staging directory; destination: str — relative destination path), "Returns"
(Path — resolved destination path beneath staging_dir), "Raises" (StageError —
if the resolved target is not under staging_dir), and note side effects such as
creating parent directories; ensure types and brief descriptions follow numpy
docstring conventions.

Comment on lines +12 to +26
def write_github_output(
file: Path, values: Mapping[str, str | Sequence[str]]
) -> None:
"""Append ``values`` to ``file`` using GitHub's multiline syntax."""

file.parent.mkdir(parents=True, exist_ok=True)
with file.open("a", encoding="utf-8") as handle:
for key, value in values.items():
delimiter = f"EOF_{uuid.uuid4().hex}"
handle.write(f"{key}<<{delimiter}\n")
if isinstance(value, Sequence) and not isinstance(value, str):
handle.write("\n".join(value))
else:
handle.write(str(value))
handle.write(f"\n{delimiter}\n")
Copy link
Contributor

Choose a reason for hiding this comment

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

🛠️ Refactor suggestion | 🟠 Major

Expand docstring with Parameters section.

Public functions must follow numpy-style docstrings including Parameters sections.

Apply this diff:

 def write_github_output(
     file: Path, values: Mapping[str, str | Sequence[str]]
 ) -> None:
-    """Append ``values`` to ``file`` using GitHub's multiline syntax."""
+    """Append ``values`` to ``file`` using GitHub's multiline syntax.
+
+    Parameters
+    ----------
+    file:
+        Path to the GitHub Actions output file (typically ``GITHUB_OUTPUT``).
+    values:
+        Mapping of output keys to string or sequence values. Sequence values
+        are joined with newlines.
+    """

As per coding guidelines.

📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
def write_github_output(
file: Path, values: Mapping[str, str | Sequence[str]]
) -> None:
"""Append ``values`` to ``file`` using GitHub's multiline syntax."""
file.parent.mkdir(parents=True, exist_ok=True)
with file.open("a", encoding="utf-8") as handle:
for key, value in values.items():
delimiter = f"EOF_{uuid.uuid4().hex}"
handle.write(f"{key}<<{delimiter}\n")
if isinstance(value, Sequence) and not isinstance(value, str):
handle.write("\n".join(value))
else:
handle.write(str(value))
handle.write(f"\n{delimiter}\n")
def write_github_output(
file: Path, values: Mapping[str, str | Sequence[str]]
) -> None:
"""Append ``values`` to ``file`` using GitHub's multiline syntax.
Parameters
----------
file:
Path to the GitHub Actions output file (typically ``GITHUB_OUTPUT``).
values:
Mapping of output keys to string or sequence values. Sequence values
are joined with newlines.
"""
file.parent.mkdir(parents=True, exist_ok=True)
with file.open("a", encoding="utf-8") as handle:
for key, value in values.items():
delimiter = f"EOF_{uuid.uuid4().hex}"
handle.write(f"{key}<<{delimiter}\n")
if isinstance(value, Sequence) and not isinstance(value, str):
handle.write("\n".join(value))
else:
handle.write(str(value))
handle.write(f"\n{delimiter}\n")
🤖 Prompt for AI Agents
.github/actions/stage/scripts/stage_common/github_output.py around lines 12 to
26: the function docstring is missing a numpy-style Parameters section; update
the docstring to include a Parameters heading that documents `file` (Path) and
`values` (Mapping[str, str | Sequence[str]]) with brief descriptions, and also
include a Returns section stating it returns None (and any Raises if
applicable). Keep the numpy-style formatting consistent with project guidelines.

Comment on lines +10 to +23
def test_public_interface(stage_common: object) -> None:
"""The package should expose the documented public API."""

expected = {
"ArtefactConfig",
"RESERVED_OUTPUT_KEYS",
"StageError",
"StageResult",
"StagingConfig",
"load_config",
"require_env_path",
"stage_artefacts",
}
assert set(stage_common.__all__) == expected
Copy link
Contributor

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟡 Minor

Add assert message to the assertion.

Line 23 uses a bare assert. The coding guidelines require: "Use assert …, "message" over bare asserts".

Apply this diff:

-    assert set(stage_common.__all__) == expected
+    assert set(stage_common.__all__) == expected, (
+        f"Public API mismatch: expected {expected}, got {set(stage_common.__all__)}"
+    )

As per coding guidelines.

📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
def test_public_interface(stage_common: object) -> None:
"""The package should expose the documented public API."""
expected = {
"ArtefactConfig",
"RESERVED_OUTPUT_KEYS",
"StageError",
"StageResult",
"StagingConfig",
"load_config",
"require_env_path",
"stage_artefacts",
}
assert set(stage_common.__all__) == expected
def test_public_interface(stage_common: object) -> None:
"""The package should expose the documented public API."""
expected = {
"ArtefactConfig",
"RESERVED_OUTPUT_KEYS",
"StageError",
"StageResult",
"StagingConfig",
"load_config",
"require_env_path",
"stage_artefacts",
}
assert set(stage_common.__all__) == expected, (
f"Public API mismatch: expected {expected}, got {set(stage_common.__all__)}"
)
🤖 Prompt for AI Agents
In tests_python/test_stage_common_module.py around lines 10 to 23, the final
assertion is a bare assert; update it to include an assertion message that
describes the mismatch (e.g., showing expected vs actual) by changing the assert
to include a comma and a descriptive string so test failures are clearer, for
example: assert set(stage_common.__all__) == expected, "public API does not
match expected: ..." but ensure message is concise and mentions expected and
actual.

Comment on lines +26 to +31
def test_stage_error_is_runtime_error(stage_common: object) -> None:
"""``StageError`` should subclass :class:`RuntimeError`."""

error = stage_common.StageError("boom")
assert isinstance(error, RuntimeError)
assert str(error) == "boom"
Copy link
Contributor

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟡 Minor

Add assert messages to all assertions.

Lines 30-31 use bare asserts. The coding guidelines require: "Use assert …, "message" over bare asserts".

Apply this diff:

-    assert isinstance(error, RuntimeError)
-    assert str(error) == "boom"
+    assert isinstance(error, RuntimeError), (
+        "StageError must subclass RuntimeError"
+    )
+    assert str(error) == "boom", (
+        f"StageError message incorrect: expected 'boom', got '{error}'"
+    )

As per coding guidelines.

📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
def test_stage_error_is_runtime_error(stage_common: object) -> None:
"""``StageError`` should subclass :class:`RuntimeError`."""
error = stage_common.StageError("boom")
assert isinstance(error, RuntimeError)
assert str(error) == "boom"
def test_stage_error_is_runtime_error(stage_common: object) -> None:
"""``StageError`` should subclass :class:`RuntimeError`."""
error = stage_common.StageError("boom")
assert isinstance(error, RuntimeError), (
"StageError must subclass RuntimeError"
)
assert str(error) == "boom", (
f"StageError message incorrect: expected 'boom', got '{error}'"
)
🤖 Prompt for AI Agents
In tests_python/test_stage_common_module.py around lines 26 to 31, the two
assertions use bare asserts; update them to include assertion messages per
guidelines. Change assert isinstance(error, RuntimeError) to assert
isinstance(error, RuntimeError), "StageError should subclass RuntimeError" and
change assert str(error) == "boom" to assert str(error) == "boom", "StageError
string representation should be 'boom'".

Comment on lines +34 to +38
def test_require_env_path_returns_path(stage_common: object, workspace: Path) -> None:
"""The environment helper should return a ``Path`` when set."""

path = stage_common.require_env_path("GITHUB_WORKSPACE")
assert path == workspace
Copy link
Contributor

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟡 Minor

Add assert message to the assertion.

Line 38 uses a bare assert. The coding guidelines require: "Use assert …, "message" over bare asserts".

Apply this diff:

-    assert path == workspace
+    assert path == workspace, (
+        f"require_env_path returned incorrect path: expected {workspace}, got {path}"
+    )

As per coding guidelines.

📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
def test_require_env_path_returns_path(stage_common: object, workspace: Path) -> None:
"""The environment helper should return a ``Path`` when set."""
path = stage_common.require_env_path("GITHUB_WORKSPACE")
assert path == workspace
def test_require_env_path_returns_path(stage_common: object, workspace: Path) -> None:
"""The environment helper should return a ``Path`` when set."""
path = stage_common.require_env_path("GITHUB_WORKSPACE")
assert path == workspace, (
f"require_env_path returned incorrect path: expected {workspace}, got {path}"
)
🤖 Prompt for AI Agents
In tests_python/test_stage_common_module.py around lines 34 to 38, the test uses
a bare assert on line 38; update the assertion to include an assertion message
following the project's guideline. Replace the bare assert path == workspace
with an assert that includes a descriptive message (e.g., stating which
environment variable was expected and showing the actual path) so the assertion
becomes assert path == workspace, "expected GITHUB_WORKSPACE to resolve to
<expected>, got <actual>" (interpolating the actual values).

Comment on lines +203 to +205
stage_common: object, workspace: Path, monkeypatch: pytest.MonkeyPatch
) -> None:
"""Absolute Windows-style globs should resolve relative to the drive root."""
Copy link
Contributor

Choose a reason for hiding this comment

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

🛠️ Refactor suggestion | 🟠 Major

Rename unused fixture parameters and drop unused noqa directives

Stop suppressing ARG001; rename unused fixture arguments to _stage_common and remove # noqa: ARG001. This keeps the fixture for import side effects without lint noise. As per coding guidelines.

-def test_match_candidate_path_handles_windows_drive(
-    stage_common: object, workspace: Path, monkeypatch: pytest.MonkeyPatch
+def test_match_candidate_path_handles_windows_drive(
+    _stage_common: object, workspace: Path, monkeypatch: pytest.MonkeyPatch
 ) -> None:
@@
-def test_glob_root_and_pattern_handles_windows_drive(stage_common: object) -> None:
+def test_glob_root_and_pattern_handles_windows_drive(_stage_common: object) -> None:
@@
-def test_glob_root_and_pattern_returns_wildcard_for_root_only(  # noqa: ARG001
-    stage_common: object,
+def test_glob_root_and_pattern_returns_wildcard_for_root_only(
+    _stage_common: object,
 ) -> None:
@@
-def test_glob_root_and_pattern_handles_posix_absolute(  # noqa: ARG001
-    stage_common: object,
+def test_glob_root_and_pattern_handles_posix_absolute(
+    _stage_common: object,
 ) -> None:
@@
-def test_glob_root_and_pattern_rejects_relative_paths(  # noqa: ARG001
-    stage_common: object,
+def test_glob_root_and_pattern_rejects_relative_paths(
+    _stage_common: object,
 ) -> None:

Also applies to: 361-365, 372-375, 386-389, 400-403

🧰 Tools
🪛 Ruff (0.13.3)

203-203: Unused function argument: stage_common

(ARG001)

🤖 Prompt for AI Agents
In tests_python/test_stage_common_staging.py around lines 203-205 (and also
apply the same change at 361-365, 372-375, 386-389, 400-403), rename the unused
fixture parameter stage_common to _stage_common in the function signatures and
remove the trailing “# noqa: ARG001” comments; keep the parameter present only
for import/side-effect purposes but mark it as unused by prefixing with an
underscore and delete the noqa suppression so the linter no longer reports
ARG001.

Comment on lines +395 to +397
root, pattern = helper(PurePosixPath("/tmp/dist/*.zip"))
assert root == "/"
assert pattern.endswith("dist/*.zip"), pattern
Copy link
Contributor

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟡 Minor

Avoid Bandit S108 by not using “/tmp” in tests

Use a neutral absolute POSIX path that is not “/tmp”, since this path is only parsed, not accessed.

-    root, pattern = helper(PurePosixPath("/tmp/dist/*.zip"))
+    root, pattern = helper(PurePosixPath("/sandbox/dist/*.zip"))
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
root, pattern = helper(PurePosixPath("/tmp/dist/*.zip"))
assert root == "/"
assert pattern.endswith("dist/*.zip"), pattern
root, pattern = helper(PurePosixPath("/sandbox/dist/*.zip"))
assert root == "/"
assert pattern.endswith("dist/*.zip"), pattern
🧰 Tools
🪛 Ruff (0.13.3)

395-395: Probable insecure usage of temporary file or directory: "/tmp/dist/*.zip"

(S108)

🤖 Prompt for AI Agents
In tests_python/test_stage_common_staging.py around lines 395 to 397, the test
uses the literal "/tmp" which triggers Bandit S108; change the input to a
neutral absolute POSIX path that won’t be interpreted as a temporary directory
(for example use "/abs/dist/*.zip" or "/some/path/dist/*.zip") since the path is
only parsed and not accessed, update the assertion accordingly to still expect
root == "/" and pattern.endswith("dist/*.zip").

@leynos
Copy link
Owner Author

leynos commented Oct 10, 2025

@coderabbitai pause

@coderabbitai
Copy link
Contributor

coderabbitai bot commented Oct 10, 2025

✅ Actions performed

Reviews paused.

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