Skip to content

Implement clean subcommand with tests; secure shell literals and UTF8 dir checks#247

Merged
leynos merged 22 commits intomainfrom
terragon/implement-clean-subcommand-qo6gzi
Dec 12, 2025
Merged

Implement clean subcommand with tests; secure shell literals and UTF8 dir checks#247
leynos merged 22 commits intomainfrom
terragon/implement-clean-subcommand-qo6gzi

Conversation

@leynos
Copy link
Copy Markdown
Owner

@leynos leynos commented Dec 9, 2025

Summary

  • Adds secure shell literals and UTF8 directory checks in test utilities to support cross-platform, safe shell usage and ensure valid paths are handled consistently.
  • Implements the clean subcommand (invoking Ninja with -t clean) with end-to-end tests, including success and failure scenarios.
  • Exposes run_ninja_tool and refactors Ninja invocation helpers to support tool-based commands.
  • Adds test support for validating tool invocations (e.g., -t clean) and wiring for tool-based tests.
  • Includes new feature file, test steps, and fixture wiring for clean scenarios.
  • Updates roadmap to reflect implementation of the clean subcommand and tooling.

Changes

  • src/runner/mod.rs
    • Use new handle_clean to implement the clean subcommand (replacing the no-op prior handling)
    • Ensure run_ninja and run_ninja_tool are publicly accessible for tests and subcommands
  • src/runner/process/mod.rs
    • Refactored Ninja invocation helpers:
      • Added configure_ninja_base, configure_ninja_command, and configure_ninja_tool_command
      • Added run_ninja_tool(program, cli, build_file, tool) to run Ninja with a specified tool (e.g., clean)
    • Existing run_ninja logic remains for manifest generation and normal build flows
  • test_support/src/check_ninja.rs
    • Expanded helpers to validate tool invocations and build-file paths
    • Added fake_ninja_expect_tool(expected_tool) to simulate Ninja confirming it was called with -t <tool>
    • Refactored to support executable scripts across Unix/Windows-like environments
  • test_support/src/env.rs
    • Minor manifest snippet adjustment to align with new tests
  • tests/features/clean.feature (new)
    • Defines scenarios for the clean subcommand, including tool invocation, failure propagation, and working directory/jobs handling
  • tests/runner_tests.rs
    • Added tests for the clean subcommand using a fake Ninja that expects the clean tool
    • Covers success, Ninja non-zero exit, missing Ninja binary, and invalid manifest scenarios
  • tests/steps/process_steps.rs
    • Added steps for creating a fake Ninja that expects the clean tool
    • Implemented run_clean to execute the subcommand and capture results for assertions
  • docs/roadmap.md
    • Marked the clean subcommand as implemented in the CLI roadmap

Test plan

  • Clean subcommand executes ninja -t clean with the expected arguments
  • Clean respects working directory flag (-C X) and jobs flag (-j N)
  • Clean propagates Ninja exit status into error messages (e.g., contains "ninja exited")
  • Tests cover missing Ninja binary (NotFound) and invalid manifest scenarios
  • Feature file and step definitions wired into integration tests for end-to-end flow

How to run tests

  • cargo test
  • For focused test groups related to clean subcommand tests, run the specific test suite in your environment as needed

📎 Task: https://www.terragonlabs.com/task/ddd65a30-f188-4294-91ee-6785072de592

📎 Task: https://www.terragonlabs.com/task/091807bd-8f6b-4ef7-9d8f-8a2f7707942c

Implemented the `clean` subcommand by generating the Ninja manifest and invoking Ninja's clean tool (`ninja -t clean`).

- Added `handle_clean` function to run the clean tool.
- Introduced `run_ninja_tool` helper for running Ninja tools.
- Updated CLI commands to handle `clean` properly.
- Added feature tests to verify clean subcommand behavior.
- Updated roadmap documentation to mark clean subcommand as done.

This enables users to remove build artefacts through the clean command.

Co-authored-by: terragon-labs[bot] <terragon-labs[bot]@users.noreply.github.com>
@sourcery-ai
Copy link
Copy Markdown
Contributor

sourcery-ai Bot commented Dec 9, 2025

Reviewer's Guide

Implements the clean subcommand by refactoring Ninja invocation into reusable helpers, adding a dedicated run_ninja_tool path, and wiring comprehensive unit/integration tests and test-support utilities to validate tool-based Ninja executions (including failure and NotFound cases), along with roadmap documentation updates.

Sequence diagram for clean subcommand invoking ninja -t clean

sequenceDiagram
    actor User
    participant Cli as Cli_main
    participant Runner as Runner_run
    participant Process as Process_module
    participant Ninja as Ninja_binary

    User->>Cli: netsuke clean [flags]
    Cli->>Runner: run(cli)
    Runner->>Runner: match cli.command
    Runner->>Runner: handle_clean(cli)

    Runner->>Runner: generate_ninja(cli)
    Runner->>Process: create_temp_ninja_file(ninja_manifest)
    Process-->>Runner: temp_build_file_path

    Runner->>Process: resolve_ninja_program()
    Process-->>Runner: ninja_program_path

    Runner->>Process: run_ninja_tool(program, cli, build_file, "clean")
    Process->>Process: configure_ninja_command(cmd, cli, build_file, Tool("clean"))
    Process->>Process: configure_ninja_base(cmd, cli, build_file)
    Process->>Ninja: spawn ninja -t clean -f build_file [-C dir] [-j N]
    Ninja-->>Process: exit_status

    Process->>Process: check_exit_status(exit_status)
    Process-->>Runner: Ok / Err(io::Error)
    Runner-->>Cli: Result
    Cli-->>User: Success output or error (includes "ninja exited" on failure)
Loading

Updated class diagram for Ninja process helpers and clean handling

classDiagram
    class Cli {
      +directory
      +jobs
    }

    class BuildTargets {
      +as_slice()
    }

    class Command {
      +current_dir(path)
      +arg(arg)
      +args(args)
      +stdout(pipe)
      +stderr(pipe)
      +spawn()
      +get_program()
    }

    class NinjaCommandType {
      <<enumeration>>
      Build(targets: BuildTargets)
      Tool(tool: str)
    }

    class ProcessModule {
      +resolve_ninja_program(): PathBuf
      +configure_ninja_base(cmd: Command, cli: Cli, build_file: Path): Result
      +configure_ninja_command(cmd: Command, cli: Cli, build_file: Path, command_type: NinjaCommandType): Result
      +run_ninja(program: Path, cli: Cli, build_file: Path, targets: BuildTargets): Result
      +run_ninja_tool(program: Path, cli: Cli, build_file: Path, tool: str): Result
      +create_temp_ninja_file(manifest: NinjaManifest): Result
    }

    class RunnerModule {
      +run(cli: Cli): Result
      +handle_build(cli: Cli, args: BuildArgs): Result
      +handle_clean(cli: Cli): Result
      +generate_ninja(cli: Cli): Result
    }

    class NinjaBinary {
      <<external>>
      +build(targets)
      +tool(tool: str)
    }

    Cli --> RunnerModule : passed_into
    RunnerModule --> ProcessModule : uses
    ProcessModule --> Command : configures
    ProcessModule --> NinjaCommandType : configures_with
    ProcessModule --> NinjaBinary : invokes
    RunnerModule --> BuildTargets : builds_with
    ProcessModule --> BuildTargets : builds_with
    RunnerModule --> Cli : reads_flags
    ProcessModule --> Cli : reads_flags
    RunnerModule --> NinjaBinary : indirect_invocation_via_run_ninja_tool
Loading

File-Level Changes

Change Details Files
Add a real clean subcommand that generates a Ninja manifest and invokes ninja -t clean via a new tool runner.
  • Replace the no-op Commands::Clean arm with handle_clean that generates a temporary Ninja manifest and calls the Ninja tool runner with clean.
  • Expose both run_ninja and the new run_ninja_tool from the runner module for reuse by subcommands and tests.
  • Wrap Ninja tool invocation errors with context describing the ninja -t clean execution and build file path.
src/runner/mod.rs
Refactor Ninja process configuration to support both build and tool invocations through shared helpers.
  • Split the Ninja command setup into configure_ninja_base (directory, jobs, build file, stdio) and a higher-level configure_ninja_command that branches on command type.
  • Introduce a NinjaCommandType enum to distinguish between standard build invocations with targets and tool invocations with -t <tool>.
  • Add run_ninja_tool(program, cli, build_file, tool) that configures the command as a tool invocation, logs it, spawns the process, streams output, and checks exit status, while keeping existing run_ninja behavior for builds.
src/runner/process/mod.rs
Extend test-support utilities to simulate Ninja behavior for build-file and tool validation in a cross-platform-friendly way.
  • Factor out a reusable make_script_executable helper to mark generated scripts executable on Unix and no-op on non-Unix platforms.
  • Update fake_ninja_check_build_file to use the new helper instead of inlined permission logic.
  • Introduce fake_ninja_expect_tool(expected_tool) to create a fake Ninja script that asserts it was invoked with -t <expected_tool> and fails otherwise.
test_support/src/check_ninja.rs
Enhance Cucumber step definitions to support clean scenarios and reduce duplication in CLI setup and result recording.
  • Add a step that installs a fake Ninja expecting the clean tool by using fake_ninja_expect_tool("clean") and the mocked PATH environment.
  • Introduce prepare_cli to ensure the manifest exists under the scenario temp directory, optionally clearing cli.directory once cli.file is absolute, and reuse it from the generic run step.
  • Introduce record_result to centralize updating CliWorld’s run_status and run_error, and use it for both build and clean runs.
  • Add a run_clean step that prepares the CLI (with directory cleared), invokes the top-level runner::run with the Clean command, and records the full error chain for assertions.
tests/steps/process_steps.rs
Add focused tests for clean subcommand behavior and generalize Ninja error handling tests for reuse across commands.
  • Extract assert_ninja_failure_propagates(command) to exercise scenarios where a fake Ninja exits non-zero and ensure the resulting error chain contains a 'ninja exited' message for both default and clean commands.
  • Extract assert_binary_not_found helper that asserts an IO NotFound error when a provided closure attempts to run a missing Ninja binary, and reuse it for both run_ninja and run_ninja_tool.
  • Add a ninja_expecting_clean rstest fixture that wires fake_ninja_expect_tool("clean") via NINJA_ENV.
  • Add unix-only tests covering: successful clean subcommand with a fake Ninja, clean propagating Ninja failures, run_ninja_tool handling of missing binaries, and clean failing on invalid manifests while reporting manifest-loading errors.
  • Update the prior run_fails_with_failing_ninja_env test to use assert_ninja_failure_propagates(None) instead of ad-hoc logic.
tests/runner_tests.rs
Define end-to-end feature coverage for the clean subcommand using the new steps and fake Ninja tooling.
  • Add tests/features/clean.feature with scenarios confirming that clean invokes Ninja with the tool flag, propagates Ninja failures with 'ninja exited', and respects -C working directory and -j jobs flags.
  • Wire these scenarios to the new step definitions that run the clean process and assert on success or error text.
tests/features/clean.feature
tests/steps/process_steps.rs
Align the test manifest helper with the updated manifest schema and mark the roadmap item as complete.
  • Simplify the manifest snippet generated by write_manifest to use a top-level command field on the hello target instead of a nested recipe.kind/command structure.
  • Mark the CLI roadmap item for implementing the clean subcommand as completed ([x]) in the documentation.
test_support/src/env.rs
docs/roadmap.md

Tips and commands

Interacting with Sourcery

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

Customizing Your Experience

Access your dashboard to:

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

Getting Help

@coderabbitai
Copy link
Copy Markdown
Contributor

coderabbitai Bot commented Dec 9, 2025

Note

Other AI code review bot(s) detected

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

Walkthrough

Summarise the change: add an explicit Clean subcommand that generates a temporary Ninja manifest and invokes Ninja with -t clean; refactor Ninja command construction into base/build/tool configurators and centralise command execution/streaming; extend Unix-only test helpers to validate -t invocations and adjust test manifest structure by removing nested recipe blocks.

Changes

Cohort / File(s) Summary
Documentation
docs/roadmap.md
Mark the "clean" subcommand checklist item as complete (toggled checked).
Runner public API & entry
src/runner/mod.rs
Export run_ninja_tool alongside run_ninja; add handle_clean to generate a temp Ninja manifest, write it to a temp file and call run_ninja_tool for the clean subcommand.
Runner process implementation
src/runner/process/mod.rs
Refactor command construction into configure_ninja_base, configure_ninja_build_command and configure_ninja_tool_command; add run_command_and_stream to log, spawn, stream and verify commands; implement run_ninja_tool(program, cli, build_file, tool); adapt run_ninja to use the new helpers.
Test helpers — fake ninja & permissions
test_support/src/check_ninja.rs
Add ToolName type and make_script_executable (Unix gated); centralise exec-bit handling; add fake_ninja_expect_tool and fake_ninja_expect_tool_with_jobs (Unix-only) to validate -t <tool> invocations; provide non-Unix stubs.
Test helpers — manifest writer
test_support/src/env.rs
Modify generated test manifest: remove nested recipe block and promote command to the immediate target level.
Feature tests
tests/features/clean.feature
Add scenarios for clean: tool invocation, failing ninja, working-directory (-C) flag, and jobs (-j) flag.
Integration & step tests
tests/runner_tests.rs, tests/steps/process_steps.rs
Add tests/fixtures and Unix-only helpers for run_ninja_tool and the clean subcommand; add helpers prepare_cli_with_directory, prepare_cli_with_absolute_file, record_result; refactor test run flow and assert error propagation and binary-not-found behaviour.
Lint attribute
src/manifest/diagnostics/yaml.rs
Add module-level #[allow(...)] attributes to silence clippy/unused_assignments false positives from derive expansions.

Sequence Diagram(s)

sequenceDiagram
    autonumber
    participant CLI as CLI handler
    participant Runner as runner::run / handle_clean
    participant Manifest as manifest generator
    participant Temp as temp file writer
    participant Process as process::run_ninja_tool
    participant Ninja as Ninja binary

    CLI->>Runner: invoke `clean` subcommand
    activate Runner
    Runner->>Manifest: generate build manifest text
    Manifest-->>Runner: return manifest text
    Runner->>Temp: write manifest to temporary file
    Temp-->>Runner: return temp build file path
    Runner->>Process: run_ninja_tool(program, cli, build_file, "clean")
    activate Process
    Process->>Process: configure_ninja_base (cwd, -j, -f)
    Process->>Process: configure_ninja_tool_command (append `-t clean`)
    Process->>Ninja: exec `ninja -f <build_file> -j <jobs> -t clean`
    Ninja-->>Process: stream stdout/stderr and return exit status
    Process-->>Runner: propagate result or error with context
    Runner-->>CLI: return success or error
    deactivate Process
    deactivate Runner
Loading

Estimated code review effort

🎯 3 (Moderate) | ⏱️ ~30–40 minutes

  • Inspect src/runner/process/mod.rs for correct propagation of working directory, job count, -f <file> and -j flags, and accurate stdout/stderr streaming.
  • Verify run_command_and_stream preserves previous streaming semantics and surfaces exit-status errors with contextual messages.
  • Check src/runner/mod.rs::handle_clean for temporary-file lifecycle and error wrapping to avoid leaking files or masking original errors.
  • Run and validate Unix-gated test helpers in test_support/src/check_ninja.rs and confirm non-Unix stubs compile.
  • Confirm manifest format change in test_support/src/env.rs produces valid Ninja files and matches updated tests.

Possibly related issues

Poem

A temp manifest, a tidy sweep,
Ninja summoned with -t clean to keep,
Helpers layered, tests that sing,
Streams that log each stdout ring,
Build dust cleared — begin again! 🧹

Pre-merge checks and finishing touches

✅ Passed checks (3 passed)
Check name Status Explanation
Docstring Coverage ✅ Passed Docstring coverage is 100.00% which is sufficient. The required threshold is 80.00%.
Title check ✅ Passed The title accurately describes the primary changes: implementing a clean subcommand with tests and addressing shell literal security and UTF-8 directory validation concerns.
Description check ✅ Passed The description comprehensively outlines the clean subcommand implementation, refactored Ninja invocation helpers, test support expansions, and includes detailed sections for changes, test plan, and how to run tests.
✨ 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 terragon/implement-clean-subcommand-qo6gzi

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

codescene-delta-analysis[bot]

This comment was marked as outdated.

@macroscopeapp
Copy link
Copy Markdown

macroscopeapp Bot commented Dec 9, 2025

Implement the clean subcommand by invoking Ninja tool '-t clean' via runner::process::run_ninja_tool and wire it into runner::run

Add runner::handle_clean to generate the build file and call runner::process::run_ninja_tool with streaming output; refactor process helpers to configure Ninja commands; add tests and test helpers to validate '-t clean', jobs, and working directory flags; update docs to mark clean implemented.

📍Where to Start

Start with runner::run and runner::handle_clean in mod.rs, then review process helpers and run_ninja_tool in mod.rs.


Macroscope summarized e1c665b.

@leynos
Copy link
Copy Markdown
Owner Author

leynos commented Dec 9, 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:

src/runner/process/mod.rs

Comment on lines +91 to +100

fn configure_ninja_command(
    cmd: &mut Command,
    cli: &Cli,
    build_file: &Path,
    targets: &BuildTargets<'_>,
) -> io::Result<()> {
    configure_ninja_base(cmd, cli, build_file)?;
    cmd.args(targets.as_slice());
    Ok(())
}

❌ New issue: Code Duplication
The module contains 2 functions with similar structure: configure_ninja_command,configure_ninja_tool_command

@leynos
Copy link
Copy Markdown
Owner Author

leynos commented Dec 9, 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/runner_tests.rs

Comment on lines +318 to +335

fn run_clean_fails_with_failing_ninja() -> Result<()> {
    let (_ninja_dir, _ninja_path, _guard) = ninja_with_exit_code(7)?;
    let (temp, manifest_path) = create_test_manifest()?;
    let cli = Cli {
        file: manifest_path.clone(),
        directory: Some(temp.path().to_path_buf()),
        command: Some(Commands::Clean),
        ..Cli::default()
    };

    let err = run(&cli).expect_err("expected clean to fail when ninja exits non-zero");
    let messages: Vec<String> = err.chain().map(ToString::to_string).collect();
    ensure!(
        messages.iter().any(|m| m.contains("ninja exited")),
        "error should report ninja exit status, got: {messages:?}"
    );
    Ok(())
}

❌ New issue: Code Duplication
The module contains 4 functions with similar structure: run_clean_fails_with_failing_ninja,run_fails_with_failing_ninja_env,run_ninja_not_found,run_ninja_tool_not_found

@leynos
Copy link
Copy Markdown
Owner Author

leynos commented Dec 9, 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/steps/process_steps.rs

Comment on lines +168 to +205

fn run_clean(world: &mut CliWorld) -> Result<()> {
    let dir = world
        .temp
        .as_ref()
        .context("CLI temp directory has not been initialised")?;
    {
        let cli = world
            .cli
            .as_mut()
            .context("CLI configuration has not been initialised")?;
        let temp_path = Utf8Path::from_path(dir.path())
            .ok_or_else(|| anyhow!("temporary directory path is not valid UTF-8"))?;
        let manifest_path = Utf8Path::from_path(&cli.file)
            .ok_or_else(|| anyhow!("CLI manifest path is not valid UTF-8"))?;
        let manifest = ensure_manifest_exists(temp_path, manifest_path)
            .context("ensure manifest exists in temp workspace")?;
        cli.file = manifest.into_std_path_buf();
        // Clear directory since file is now an absolute path; otherwise
        // resolve_manifest_path would incorrectly join them.
        cli.directory = None;
    }
    let cli = world
        .cli
        .as_ref()
        .context("CLI configuration has not been initialised")?;
    match runner::run(cli) {
        Ok(()) => {
            world.run_status = Some(true);
            world.run_error = None;
        }
        Err(e) => {
            world.run_status = Some(false);
            // Use alternate formatting to capture the full anyhow error chain.
            world.run_error = Some(format!("{e:#}"));
        }
    }
    Ok(())
}

❌ New issue: Code Duplication
The module contains 2 functions with similar structure: run,run_clean

@coderabbitai

This comment was marked as resolved.

@coderabbitai

This comment was marked as resolved.

@coderabbitai

This comment was marked as resolved.

…est setup

- Introduced NinjaCommandType enum to unify configuration of build and tool commands.
- Replaced separate configure functions with a single configure_ninja_command using NinjaCommandType.
- Centralized CLI preparation and result recording in test steps to reduce duplication.
- Added helper to assert Ninja failure propagation in tests.
- Improved error reporting consistency on Ninja non-zero exit statuses.

Co-authored-by: terragon-labs[bot] <terragon-labs[bot]@users.noreply.github.com>
codescene-delta-analysis[bot]

This comment was marked as outdated.

@leynos
Copy link
Copy Markdown
Owner Author

leynos commented Dec 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/runner_tests.rs

Comment on lines +331 to +340

fn run_ninja_tool_not_found() -> Result<()> {
    let cli = Cli::default();
    let err = run_ninja_tool(
        Path::new("does-not-exist"),
        &cli,
        Path::new("build.ninja"),
        "clean",
    )
    .err()
    .context("expected run_ninja_tool to fail when binary is missing")?;

❌ New issue: Code Duplication
The module contains 2 functions with similar structure: run_ninja_not_found,run_ninja_tool_not_found

@coderabbitai

This comment was marked as resolved.

The clippy expect attribute on the test helper function was reformatted from a single line to multiple lines for improved readability and consistency.

Co-authored-by: terragon-labs[bot] <terragon-labs[bot]@users.noreply.github.com>
@leynos leynos changed the title Implement clean subcommand with tests and Ninja tool invocation Implement clean subcommand with tests and Ninja tool support Dec 10, 2025
codescene-delta-analysis[bot]

This comment was marked as outdated.

Refactored tests for run_ninja and run_ninja_tool when the binary is missing by extracting common assertion logic into a new helper function `assert_binary_not_found`. This reduces code duplication and improves readability of failure checks for NotFound errors.

Co-authored-by: terragon-labs[bot] <terragon-labs[bot]@users.noreply.github.com>
@leynos leynos marked this pull request as ready for review December 10, 2025 18:59
Copy link
Copy Markdown
Contributor

@sourcery-ai sourcery-ai Bot left a comment

Choose a reason for hiding this comment

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

Hey there - I've reviewed your changes - here's some feedback:

  • The new fake_ninja_expect_tool helper always writes a POSIX sh script and is used in step definitions without #[cfg(unix)], which will fail on Windows runners; consider either adding a Windows-specific script variant (e.g., .bat) or guarding the usage and function with #[cfg(unix)] as you did for some of the tests.
  • The prepare_cli(world, clear_directory: bool) helper uses a boolean flag to alter behavior, which makes call sites less self-documenting; consider splitting this into two explicit helpers (e.g., prepare_cli_with_directory and prepare_cli_with_absolute_file) or an enum to clarify intent at the call site.
Prompt for AI Agents
Please address the comments from this code review:

## Overall Comments
- The new `fake_ninja_expect_tool` helper always writes a POSIX `sh` script and is used in step definitions without `#[cfg(unix)]`, which will fail on Windows runners; consider either adding a Windows-specific script variant (e.g., `.bat`) or guarding the usage and function with `#[cfg(unix)]` as you did for some of the tests.
- The `prepare_cli(world, clear_directory: bool)` helper uses a boolean flag to alter behavior, which makes call sites less self-documenting; consider splitting this into two explicit helpers (e.g., `prepare_cli_with_directory` and `prepare_cli_with_absolute_file`) or an enum to clarify intent at the call site.

## Individual Comments

### Comment 1
<location> `tests/runner_tests.rs:320-322` </location>
<code_context>
 use tempfile::TempDir;

+/// Make a script file executable on Unix platforms.
+#[cfg(unix)]
+fn make_script_executable(path: &PathBuf, context: &str) -> Result<()> {
+    use std::os::unix::fs::PermissionsExt;
</code_context>

<issue_to_address>
**suggestion (testing):** Add a more targeted assertion that `run_ninja_tool` passes the expected arguments (e.g. `-f`, `-C`, `-j`) when invoked via the clean subcommand.

The current test only validates that `-t clean` is passed, so it would still succeed if `run_ninja_tool` stopped providing other required flags like `-f <build_file>` or `-C`/`-j`. Please either extend `fake_ninja_expect_tool` to assert these additional arguments, or add a dedicated test that checks the full argument vector passed to `run_ninja_tool`, so regressions in the new configuration logic are caught reliably.
</issue_to_address>

### Comment 2
<location> `src/runner/process/mod.rs:91` </location>
<code_context>
     Ok(())
 }

+/// Specifies whether Ninja should run a build or invoke a tool subcommand.
+enum NinjaCommandType<'a> {
+    /// Standard build invocation with optional targets.
</code_context>

<issue_to_address>
**issue (complexity):** Consider replacing the enum-based `NinjaCommandType` with two explicit helper functions for build and tool commands to keep the shared base setup while simplifying the control flow and call sites.

The enum-based `NinjaCommandType<'a>` does add indirection and a lifetime that don’t buy much for the two current call sites. You can keep the useful `configure_ninja_base` extraction while simplifying the rest by splitting into two thin helpers and removing the enum entirely.

For example:

```rust
fn configure_ninja_base(cmd: &mut Command, cli: &Cli, build_file: &Path) -> io::Result<()> {
    if let Some(dir) = &cli.directory {
        let canonical = canonicalize_utf8_path(dir.as_path())?;
        cmd.current_dir(canonical.as_std_path());
    }
    if let Some(jobs) = cli.jobs {
        cmd.arg("-j").arg(jobs.to_string());
    }
    let build_file_path = canonicalize_utf8_path(build_file).or_else(|_| {
        Utf8PathBuf::from_path_buf(build_file.to_path_buf()).map_err(|_| {
            io::Error::new(
                ErrorKind::InvalidData,
                format!(
                    "build file path {} is not valid UTF-8",
                    build_file.display()
                ),
            )
        })
    })?;
    cmd.arg("-f").arg(build_file_path.as_std_path());
    cmd.stdout(Stdio::piped());
    cmd.stderr(Stdio::piped());
    Ok(())
}
```

Then replace `NinjaCommandType` + `configure_ninja_command` with two explicit helpers:

```rust
fn configure_ninja_build_command(
    cmd: &mut Command,
    cli: &Cli,
    build_file: &Path,
    targets: &BuildTargets<'_>,
) -> io::Result<()> {
    configure_ninja_base(cmd, cli, build_file)?;
    cmd.args(targets.as_slice());
    Ok(())
}

fn configure_ninja_tool_command(
    cmd: &mut Command,
    cli: &Cli,
    build_file: &Path,
    tool: &str,
) -> io::Result<()> {
    configure_ninja_base(cmd, cli, build_file)?;
    cmd.arg("-t").arg(tool);
    Ok(())
}
```

Call sites become simpler and more self-documenting:

```rust
pub fn run_ninja(
    program: &Path,
    cli: &Cli,
    build_file: &Path,
    targets: &BuildTargets<'_>,
) -> io::Result<()> {
    let mut cmd = Command::new(program);
    configure_ninja_build_command(&mut cmd, cli, build_file, targets)?;
    log_command_execution(&cmd);
    let child = cmd.spawn()?;
    let status = spawn_and_stream_output(child)?;
    check_exit_status(status)
}

pub fn run_ninja_tool(
    program: &Path,
    cli: &Cli,
    build_file: &Path,
    tool: &str,
) -> io::Result<()> {
    let mut cmd = Command::new(program);
    configure_ninja_tool_command(&mut cmd, cli, build_file, tool)?;
    log_command_execution(&cmd);
    let child = cmd.spawn()?;
    let status = spawn_and_stream_output(child)?;
    check_exit_status(status)
}
```

This keeps all current behavior (including the new tool invocation feature), while:

- Removing the enum and its lifetime parameter.
- Making each path (`build` vs `tool`) explicit and easier to follow.
- Preserving the shared setup in `configure_ninja_base`.
</issue_to_address>

Sourcery is free for open source - if you like our reviews please consider sharing them ✨
Help me be more useful! Please click 👍 or 👎 on each comment and I'll use the feedback to improve your reviews.

Comment thread tests/runner_tests.rs
Comment thread src/runner/process/mod.rs Outdated
…rate functions

Replaced the NinjaCommandType enum with separate functions for configuring
standard build commands and tool commands to simplify command setup and
improve clarity.

Additionally, enhanced test support by improving the fake ninja tool script
validation to check for both -t <tool> and -f <build_file> arguments.
Updated process step tests to better manage CLI directory and file path preparations.

Co-authored-by: terragon-labs[bot] <terragon-labs[bot]@users.noreply.github.com>
codescene-delta-analysis[bot]

This comment was marked as outdated.

@leynos
Copy link
Copy Markdown
Owner Author

leynos commented Dec 10, 2025

@coderabbitai review

@coderabbitai
Copy link
Copy Markdown
Contributor

coderabbitai Bot commented Dec 10, 2025

✅ Actions performed

Review triggered.

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

Copy link
Copy Markdown
Contributor

@coderabbitai coderabbitai Bot left a comment

Choose a reason for hiding this comment

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

Actionable comments posted: 2

Caution

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

⚠️ Outside diff range comments (1)
src/runner/process/mod.rs (1)

142-174: File exceeds 400-line limit.

At 419 lines, this file breaches the 400-line maximum specified in the coding guidelines. The CountingReader, CountingWriter, and ForwardStats types (lines 225–315) are candidates for extraction into a dedicated io_forwarding submodule.

As per coding guidelines: "Files must not exceed 400 lines in length."

♻️ Duplicate comments (1)
tests/runner_tests.rs (1)

320-334: Reasonable coverage for success path.

The test validates that the clean subcommand succeeds with the ninja_expecting_clean fixture, which already asserts both -t clean and -f <build_file> are present.

A previous review suggested extending assertions to cover -C/-j flags. This remains a valid enhancement for comprehensive argument validation but is not blocking for the success case with default CLI options.

📜 Review details

Configuration used: CodeRabbit UI

Review profile: ASSERTIVE

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between 0a19be8 and b6dfb7d.

📒 Files selected for processing (8)
  • docs/roadmap.md (1 hunks)
  • src/runner/mod.rs (3 hunks)
  • src/runner/process/mod.rs (3 hunks)
  • test_support/src/check_ninja.rs (2 hunks)
  • test_support/src/env.rs (1 hunks)
  • tests/features/clean.feature (1 hunks)
  • tests/runner_tests.rs (4 hunks)
  • tests/steps/process_steps.rs (4 hunks)
🧰 Additional context used
📓 Path-based instructions (5)
**/*.rs

📄 CodeRabbit inference engine (AGENTS.md)

**/*.rs: Clippy warnings MUST be disallowed in Rust code.
Fix any warnings emitted during Rust tests in the code itself rather than silencing them.
In Rust, extract meaningfully named helper functions when a function is too long, adhering to separation of concerns and CQRS.
In Rust, group related parameters in meaningfully named structs when a function has too many parameters.
In Rust, consider using Arc to reduce data returned when a function is returning a large error.
Every Rust module must begin with a module level (//!) comment explaining the module's purpose and utility.
Document public APIs in Rust using Rustdoc comments (///) so documentation can be generated with cargo doc.
In Rust, prefer immutable data and avoid unnecessary mut bindings.
In Rust, handle errors with the Result type instead of panicking where feasible.
Avoid unsafe code in Rust unless absolutely necessary, and document any usage clearly.
In Rust, place function attributes after doc comments.
In Rust, do not use return in single-line functions.
In Rust, use predicate functions for conditional criteria with more than two branches.
Lints in Rust must not be silenced except as a last resort.
Lint rule suppressions in Rust must be tightly scoped and include a clear reason.
In Rust, prefer expect over allow.
In Rust tests, use rstest fixtures for shared setup.
In Rust tests, replace duplicated tests with #[rstest(...)] parameterized cases.
In Rust, prefer mockall for mocks and stubs.
In Rust, use concat!() to combine long string literals rather than escaping newlines with a backslash.
In Rust, prefer single line versions of functions where appropriate (e.g., pub fn new(id: u64) -> Self { Self(id) } instead of multi-line).
Use NewTypes in Rust to model domain values and eliminate 'integer soup'. Reach for newt-hype when introducing many homogeneous wrappers; add small shims for string-backed wrappers. For path-centric wrappers implement AsRef<Path> along...

Files:

  • test_support/src/env.rs
  • tests/steps/process_steps.rs
  • src/runner/mod.rs
  • test_support/src/check_ninja.rs
  • tests/runner_tests.rs
  • src/runner/process/mod.rs

⚙️ CodeRabbit configuration file

**/*.rs: * Seek to keep the cognitive complexity of functions no more than 9.

  • 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() in tests.
  • .expect() and .unwrap() are forbidden outside of tests. Errors must be propagated.
  • 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...

Files:

  • test_support/src/env.rs
  • tests/steps/process_steps.rs
  • src/runner/mod.rs
  • test_support/src/check_ninja.rs
  • tests/runner_tests.rs
  • src/runner/process/mod.rs
docs/**/*.md

📄 CodeRabbit inference engine (AGENTS.md)

docs/**/*.md: When new decisions are made, requirements change, libraries are added/removed, or architectural patterns evolve, proactively update the relevant file(s) in the docs/ directory to reflect the latest state.
Documentation must use en-GB-oxendict spelling and grammar ("-ize" / "-yse" / "-our"), with exception for the LICENSE filename.

docs/**/*.md: Use British English (en-GB) spelling with -ize endings (realize, organization), -lyse endings (analyse, paralyse, catalyse), -our endings (colour, behaviour), -re endings (calibre, centre, fibre), double 'l' (cancelled, counsellor), maintained 'e' (likeable, liveable), and -ogue endings (analogue, catalogue)
Use the Oxford comma in documentation: 'ships, planes, and hovercraft'
Treat company names as collective nouns in documentation: 'Concordat Industries are expanding'
Write headings in sentence case in documentation
Use Markdown headings (#, ##, ###) in order without skipping levels
Follow markdownlint recommendations for Markdown formatting
Provide code blocks and lists using standard Markdown syntax
Always provide a language identifier for fenced code blocks; use plaintext for non-code text
Use - as the first level bullet and renumber lists when items change in documentation
Prefer inline links using [text](url) or angle brackets around the URL in documentation
Ensure blank lines before and after bulleted lists and fenced blocks in documentation
Ensure tables have a delimiter line below the header row in documentation
Expand any uncommon acronym on first use in documentation (e.g., Continuous Integration (CI))
Wrap paragraphs at 80 columns in documentation
Wrap code at 120 columns in documentation
Do not wrap tables in documentation
Use GitHub-flavoured numeric footnotes referenced as [^1] in documentation
Number footnotes in order of appearance in the document
Caption every table and every diagram in documentation
Use ![alt text](path/to/image) for embedding figures with brief alt te...

Files:

  • docs/roadmap.md
**/*.md

📄 CodeRabbit inference engine (AGENTS.md)

**/*.md: Markdown paragraphs and bullet points must be wrapped at 80 columns.
Code blocks in Markdown must be wrapped at 120 columns.
Tables and headings in Markdown must not be wrapped.
Use dashes (-) for list bullets in Markdown.
Use GitHub-flavoured Markdown footnotes ([^1]) for references and footnotes.

Files:

  • 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/roadmap.md
docs/**/{README,readme,GUIDE,guide,*.md}

📄 CodeRabbit inference engine (docs/adr-001-replace-serde-yml-with-serde-saphyr.md)

Document in Netsuke's README and user guide that the project now supports YAML 1.2 (not YAML 1.1) and that duplicate keys in manifests are no longer permitted

Files:

  • docs/roadmap.md
docs/**/!(README).md

📄 CodeRabbit inference engine (docs/documentation-style-guide.md)

Avoid first and second person personal pronouns outside the README.md file in documentation

Files:

  • docs/roadmap.md
🧬 Code graph analysis (3)
tests/steps/process_steps.rs (3)
test_support/src/check_ninja.rs (1)
  • fake_ninja_expect_tool (79-119)
test_support/src/env.rs (1)
  • mocked_path_env (53-60)
test_support/src/lib.rs (1)
  • ensure_manifest_exists (194-213)
src/runner/mod.rs (2)
src/runner/process/mod.rs (3)
  • run_ninja (143-155)
  • run_ninja_tool (167-174)
  • resolve_ninja_program (58-60)
src/runner/process/file_io.rs (1)
  • create_temp_ninja_file (13-29)
tests/runner_tests.rs (4)
src/runner/mod.rs (4)
  • run (85-103)
  • default (75-77)
  • new (32-34)
  • new (55-57)
src/runner/process/mod.rs (3)
  • run_ninja (143-155)
  • run_ninja_tool (167-174)
  • new (351-353)
test_support/src/check_ninja.rs (1)
  • fake_ninja_expect_tool (79-119)
test_support/src/env.rs (1)
  • override_ninja_env (249-260)
🔍 Remote MCP

Based on my exploration of the Netsuke repository documentation and architecture, I can now provide you with a comprehensive summary of additional context relevant to reviewing this PR.

Additional Context Found

Repository Architecture and Related Work

Clean Subcommand Architecture:
The PR implements the clean subcommand as described in the related issue #158, which proposed a command-builder refactoring. The implementation creates:

  • A new handle_clean() function that generates a Ninja manifest and invokes it with the clean tool (-t clean)
  • A public run_ninja_tool() function that enables tool-based command execution
  • Refactored helper functions: configure_ninja_base(), configure_ninja_build_command(), and configure_ninja_tool_command() to support both standard builds and tool invocations

Runner Module Responsibilities:
The runner module acts as the central orchestrator between CLI arguments and the Ninja build system. It handles:

  • Command dispatch (Build, Clean, Graph, Manifest subcommands)
  • Manifest generation through a four-stage pipeline (path resolution, loading, IR generation, code generation)
  • Temporary file management using RAII patterns
  • Subprocess execution with proper error context propagation

Test Architecture Pattern:
The codebase employs a comprehensive three-layer testing strategy:

  • Unit tests with rstest fixtures for core algorithms
  • BDD tests with Cucumber for user-facing behavior specifications
  • Snapshot tests with insta for regression detection of generated IR and Ninja files

Review Comment Context

The review comments identify legitimate code duplication patterns that the PR introduces:

  1. configure_ninja_command vs configure_ninja_tool_command similarity - Both build Ninja command structures with overlapping logic that could be consolidated through an enum-based approach
  2. Test helper duplication - Multiple tests (run_clean_fails_with_failing_ninja, run_fails_with_failing_ninja_env, etc.) share identical setup and assertion patterns
  3. Path preparation duplication in steps - Both run and run_clean prepare CLI with manifest paths and record results similarly

Design Philosophy Alignment

The PR aligns with Netsuke's core design principles:

  • Security by default: Commands are shell-escaped during IR generation, not at Ninja execution time
  • Separation of concerns: The runner delegates manifest loading, IR generation, and code generation to specialized modules
  • Deterministic execution: The clean command follows the same deterministic pipeline as build commands
  • Comprehensive error handling: Uses anyhow::Context for error propagation with actionable messages

Implementation Quality Standards

Per AGENTS.md guidelines, the codebase enforces:

  • Strict clippy linting (deny warnings for panic-prone operations, portability, unwrap usage)
  • No direct std::fs or std::path usage (mandate cap-std and camino)
  • Error types: thiserror for domain errors, anyhow only at application boundary
  • File maximum 400 lines per function for maintainability
  • Test isolation through temporary directories and environment variable mocking
⏰ Context from checks skipped due to timeout of 120000ms. You can increase the timeout in your CodeRabbit configuration to a maximum of 15 minutes (900000ms). (1)
  • GitHub Check: Sourcery review
🔇 Additional comments (23)
docs/roadmap.md (1)

182-182: LGTM!

The roadmap correctly marks the clean subcommand as complete, consistent with the implementation introduced in this PR.

test_support/src/env.rs (1)

179-189: LGTM!

The simplified manifest structure removes the nested recipe block and promotes command to a direct target field. This aligns with the updated manifest parsing expectations used by the new tool-based invocation workflow.

src/runner/process/mod.rs (1)

62-89: LGTM!

The configure_ninja_base helper cleanly encapsulates common setup logic (working directory, job count, build file path, and I/O pipes). The UTF-8 path validation with fallback is appropriately defensive.

test_support/src/check_ninja.rs (1)

9-25: LGTM!

The make_script_executable helper properly abstracts platform-specific permission handling. The Unix implementation sets 0o755 with appropriate error context, whilst the non-Unix variant is a harmless no-op.

tests/features/clean.feature (1)

1-29: LGTM!

The feature file provides solid BDD coverage for the clean subcommand:

  • Happy path with tool flag validation
  • Error propagation when Ninja fails
  • Working directory (-C) flag handling
  • Jobs (-j) flag handling

The scenarios use consistent structure and align with the implementation's CLI options.

src/runner/mod.rs (3)

24-24: LGTM!

Public export of run_ninja_tool aligns with the PR objective to enable tool-based Ninja invocations from tests and subcommands.


97-97: LGTM!

The Clean arm now correctly delegates to handle_clean, replacing the previous no-op implementation.


159-183: LGTM!

The handle_clean implementation correctly:

  • Generates the Ninja manifest via generate_ninja.
  • Holds the temporary file in scope until run_ninja_tool completes.
  • Provides actionable error context on failure.
  • Uses en-GB spelling ("artefacts").
tests/steps/process_steps.rs (6)

64-71: LGTM!

The Unix-only step correctly sets up a fake Ninja binary that validates the -t clean tool invocation, following the established pattern for test ninja installation.


101-122: LGTM!

The prepare_cli_with_directory helper correctly extracts common CLI preparation logic, with clear error context and proper UTF-8 path validation.


124-137: LGTM!

The prepare_cli_with_absolute_file helper correctly composes prepare_cli_with_directory and clears the directory setting to prevent path-joining issues. The documentation clearly explains the rationale.


139-151: LGTM!

The record_result helper cleanly encapsulates the common pattern of updating world.run_status and world.run_error based on command execution outcome.


173-190: LGTM!

The refactored run step correctly uses the extracted helpers, reducing duplication and improving clarity. The error propagation via ? and result recording follow best practices.


192-209: LGTM!

The Unix-only run_clean step correctly:

  • Uses prepare_cli_with_absolute_file since runner::run handles path resolution internally.
  • Captures the full anyhow error chain with alternate formatting ({e:#}).
  • Records results via the shared record_result helper.
tests/runner_tests.rs (9)

5-5: LGTM!

Import of run_ninja_tool enables the new test coverage for tool-based Ninja invocations.


97-119: LGTM!

The assert_ninja_failure_propagates helper correctly extracts the common pattern for testing ninja failure propagation, reducing test duplication as noted in the PR comments. The #[expect(clippy::expect_used)] is appropriately scoped with a clear reason.


121-135: LGTM!

The generic assert_binary_not_found helper cleanly extracts the common NotFound assertion pattern, enabling reuse across run_ninja and run_ninja_tool tests as suggested in the PR comments.


137-149: LGTM!

The refactored run_ninja_not_found test cleanly uses the assert_binary_not_found helper, making the test intent clear and reducing duplication.


302-305: LGTM!

The refactored run_fails_with_failing_ninja_env test now delegates to assert_ninja_failure_propagates, eliminating duplication while preserving test coverage.


307-318: LGTM!

The ninja_expecting_clean fixture follows the established pattern for test fixtures, correctly setting up a fake Ninja that validates the -t clean tool invocation.


336-340: LGTM!

The run_clean_fails_with_failing_ninja test cleanly uses the shared helper to verify failure propagation for the clean subcommand.


342-354: LGTM!

The run_ninja_tool_not_found test correctly uses assert_binary_not_found to verify NotFound error handling, following the established pattern.


356-377: LGTM!

The run_clean_fails_with_invalid_manifest test correctly verifies that the clean subcommand reports manifest loading errors with actionable context.

Comment thread src/runner/process/mod.rs
Comment thread test_support/src/check_ninja.rs
@leynos
Copy link
Copy Markdown
Owner Author

leynos commented Dec 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:

src/runner/process/mod.rs

Comment on lines +91 to +100

fn configure_ninja_build_command(
    cmd: &mut Command,
    cli: &Cli,
    build_file: &Path,
    targets: &BuildTargets<'_>,
) -> io::Result<()> {
    configure_ninja_base(cmd, cli, build_file)?;
    cmd.args(targets.as_slice());
    Ok(())
}

❌ New issue: Code Duplication
The module contains 2 functions with similar structure: configure_ninja_build_command,configure_ninja_tool_command

@coderabbitai

This comment was marked as resolved.

@leynos leynos changed the title Implement clean subcommand with tests and Ninja tool support Implement clean subcommand with tests Dec 11, 2025
codescene-delta-analysis[bot]

This comment was marked as outdated.

…sions in YamlDiagnostic

Add attribute annotations to suppress 'unused_assignments' lint warnings caused by miette/thiserror derive macro false positives on YamlDiagnostic struct fields.

Co-authored-by: terragon-labs[bot] <terragon-labs[bot]@users.noreply.github.com>
codescene-delta-analysis[bot]

This comment was marked as outdated.

… lint

Remove redundant #[allow(unfulfilled_lint_expectations)] and #[expect] attributes,
and replace with a single #[allow(unused_assignments)] on YamlDiagnostic struct.
This addresses a false positive from miette/thiserror derive macros causing
unused_assignments lint in certain Rust versions.

Co-authored-by: terragon-labs[bot] <terragon-labs[bot]@users.noreply.github.com>
codescene-delta-analysis[bot]

This comment was marked as outdated.

…used assignments

- Added `unused_assignments` to allowed Clippy lints in yaml.rs.
- Removed redundant `#[allow(unused_assignments)]` attribute before the error struct.

This change addresses inconsistent lint warnings caused by derive macros expanding field assignments differently across Rust versions, improving lint stability without affecting functionality.

Co-authored-by: terragon-labs[bot] <terragon-labs[bot]@users.noreply.github.com>
codescene-delta-analysis[bot]

This comment was marked as outdated.

@leynos
Copy link
Copy Markdown
Owner Author

leynos commented Dec 12, 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:

Large Method

test_support/src/check_ninja.rs: build_tool_validation_script

What lead to degradation?

build_tool_validation_script has 101 lines, threshold = 70

Why does this problem occur?

Overly long functions make the code harder to read. The recommended maximum function length for the Rust language is 70 lines of code. Severity: Brain Method - Complex Method - Long Method.

How to fix it?

We recommend to be careful here -- just splitting long functions don't necessarily make the code easier to read. Instead, look for natural chunks inside the functions that expresses a specific task or concern. Often, such concerns are indicated by a Code Comment followed by an if-statement. Use the EXTRACT FUNCTION refactoring to encapsulate that concern.

@coderabbitai

This comment was marked as resolved.

…ular flag checks

Refactored the shell script generation for ninja tool validation in test_support/src/check_ninja.rs. Introduced reusable functions and a FlagValidation struct to modularize initialization, loop checking, and validation of optional flags (-j for jobs and -C for directory). This simplifies adding or modifying flag validations and reduces code duplication while preserving existing behavior.

Co-authored-by: terragon-labs[bot] <terragon-labs[bot]@users.noreply.github.com>
codescene-delta-analysis[bot]

This comment was marked as outdated.

@leynos
Copy link
Copy Markdown
Owner Author

leynos commented Dec 12, 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:

test_support/src/check_ninja.rs

Comment on file

//! Helpers for validating build file paths via fake Ninja binaries.
//! Helpers for validating build file paths and tool invocations via fake Ninja binaries.

❌ New issue: String Heavy Function Arguments
In this module, 40.7% of all arguments to its 16 functions are strings. The threshold for string arguments is 39.0%

Use NewTypes to model domain values and eliminate "string soup". Reach for newt-hype when introducing many homogeneous wrappers that share behaviour; add small shims such as From<&str> and AsRef for string-backed wrappers. For path-centric wrappers implement AsRef alongside into_inner() and to_path_buf(), avoid attempting impl From for PathBuf because of the orphan rule. Prefer explicit tuple structs whenever bespoke validation or tailored trait surfaces are required, customizing Deref, AsRef, and TryFrom per type. Use the-newtype when defining traits and needing blanket implementations that apply across wrappers satisfying Newtype + AsRef/AsMut, or when establishing a coherent internal convention that keeps trait forwarding consistent without per-type boilerplate. Combine approaches: lean on newt-hype for the common case, tuple structs for outliers, and the-newtype to unify behaviour when owning the trait definitions.

Copy link
Copy Markdown
Contributor

@coderabbitai coderabbitai Bot left a comment

Choose a reason for hiding this comment

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

Actionable comments posted: 1

♻️ Duplicate comments (1)
src/manifest/diagnostics/yaml.rs (1)

14-18: Replace module-level #![allow(...)] with scoped #[expect] on the struct.

The module-level lint suppression violates the explicit coding guideline: "#[allow] is forbidden. Only narrowly scoped #[expect(lint, reason = '...')] is allowed." The justification in the comment is incorrect—scope the #[expect] to the YamlDiagnostic struct at lines 81-94, not at module level. If the lint doesn't fire in some versions, a narrowly-scoped #[expect] will handle that correctly.

Apply this diff (remove module-level suppression, add scoped expectation to struct):

-// Module-level suppression for version-dependent lint false positives from
-// miette/thiserror derive macros. The unused_assignments lint fires in some
-// Rust versions but not others. Since `#[expect]` fails when the lint doesn't
-// fire, and `unfulfilled_lint_expectations` cannot be expected, we must use
-// `#[allow]` here. FIXME: remove once upstream is fixed.
-#![allow(
-    clippy::allow_attributes,
-    clippy::allow_attributes_without_reason,
-    unused_assignments
-)]
-
 use super::{ManifestName, ManifestSource};

Then at the struct definition:

+// Expect a version-dependent unused_assignments false positive from the
+// miette/thiserror derive expansion. FIXME: remove once upstream is fixed or
+// link to specific issue.
+#[expect(
+    unused_assignments,
+    reason = "version-dependent false positive from miette/thiserror derive expansion"
+)]
 #[derive(Debug, Error, Diagnostic)]
 #[error("{message}")]
 #[diagnostic(code(netsuke::yaml::parse))]
 struct YamlDiagnostic {
🤖 Prompt for AI Agents
In src/manifest/diagnostics/yaml.rs, remove the module-level #![allow(...)] block at lines 14-18 and its associated comment at lines 9-13. Then, at the YamlDiagnostic struct definition (around line 81), add a narrowly scoped #[expect(unused_assignments, reason = "version-dependent false positive from miette/thiserror derive expansion")] attribute immediately before the #[derive(Debug, Error, Diagnostic)] line, along with a brief comment explaining the version-dependent false positive and a FIXME to remove once upstream is fixed. Do not include any lint groups or clippy::allow_attributes. Run cargo clippy and cargo test to verify the change.
📜 Review details

Configuration used: CodeRabbit UI

Review profile: ASSERTIVE

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between b3885a9 and 0afdac7.

📒 Files selected for processing (2)
  • src/manifest/diagnostics/yaml.rs (1 hunks)
  • test_support/src/check_ninja.rs (2 hunks)
🧰 Additional context used
📓 Path-based instructions (1)
**/*.rs

📄 CodeRabbit inference engine (AGENTS.md)

**/*.rs: Clippy warnings MUST be disallowed in Rust code.
Fix any warnings emitted during Rust tests in the code itself rather than silencing them.
In Rust, extract meaningfully named helper functions when a function is too long, adhering to separation of concerns and CQRS.
In Rust, group related parameters in meaningfully named structs when a function has too many parameters.
In Rust, consider using Arc to reduce data returned when a function is returning a large error.
Every Rust module must begin with a module level (//!) comment explaining the module's purpose and utility.
Document public APIs in Rust using Rustdoc comments (///) so documentation can be generated with cargo doc.
In Rust, prefer immutable data and avoid unnecessary mut bindings.
In Rust, handle errors with the Result type instead of panicking where feasible.
Avoid unsafe code in Rust unless absolutely necessary, and document any usage clearly.
In Rust, place function attributes after doc comments.
In Rust, do not use return in single-line functions.
In Rust, use predicate functions for conditional criteria with more than two branches.
Lints in Rust must not be silenced except as a last resort.
Lint rule suppressions in Rust must be tightly scoped and include a clear reason.
In Rust, prefer expect over allow.
In Rust tests, use rstest fixtures for shared setup.
In Rust tests, replace duplicated tests with #[rstest(...)] parameterized cases.
In Rust, prefer mockall for mocks and stubs.
In Rust, use concat!() to combine long string literals rather than escaping newlines with a backslash.
In Rust, prefer single line versions of functions where appropriate (e.g., pub fn new(id: u64) -> Self { Self(id) } instead of multi-line).
Use NewTypes in Rust to model domain values and eliminate 'integer soup'. Reach for newt-hype when introducing many homogeneous wrappers; add small shims for string-backed wrappers. For path-centric wrappers implement AsRef<Path> along...

Files:

  • src/manifest/diagnostics/yaml.rs
  • test_support/src/check_ninja.rs

⚙️ CodeRabbit configuration file

**/*.rs: * Seek to keep the cognitive complexity of functions no more than 9.

  • 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() in tests.
  • .expect() and .unwrap() are forbidden outside of tests. Errors must be propagated.
  • 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...

Files:

  • src/manifest/diagnostics/yaml.rs
  • test_support/src/check_ninja.rs
🔍 Remote MCP

Summary

I've successfully retrieved comprehensive documentation from the Netsuke repository that provides substantial context for reviewing the "Implement clean subcommand" pull request (#247). Here's what I found:

Key Context for PR Review:

Architecture & Design:

  • Netsuke is a build system compiler that transforms YAML manifests with Jinja2 templating into Ninja build files
  • Uses a six-stage pipeline: ingestion → YAML parse → template expansion → deserialization → IR generation → Ninja synthesis
  • The runner module orchestrates the complete pipeline

PR-Relevant Implementation Details:

  1. Command Runner Architecture - The PR adds a clean subcommand by extending the command dispatch system in src/runner.rs. The existing handle_build() pattern shows how commands are orchestrated.

  2. Process Module - The PR introduces run_ninja_tool() as a complement to the existing run_ninja(). The documentation confirms the process module handles subprocess invocation with proper error handling and context propagation using anyhow::Context.

  3. Test Support Infrastructure - The documentation details:

    • BDD testing via cucumber with CliWorld state management
    • test_support crate providing fake ninja executables for testing
    • Snapshot testing with insta for output validation
    • The PR extends this with fake_ninja_expect_tool() functions
  4. Security & Design Patterns:

    • Shell-quote escaping for command safety (shell-quote crate)
    • POSIX sh mode validation with shlex
    • Capability-based I/O through cap-std for sandboxed filesystem access
    • UTF-8 path handling via camino
  5. Error Handling:

    • Three-layer model: domain errors (thiserror) → context propagation (anyhow) → user-facing diagnostics
    • The PR's test helpers properly follow this pattern

Code Quality Standards:

  • Strict clippy configuration with panic-prone operation denial
  • #[cfg(unix)] guards for platform-specific code (as implemented in the PR)
  • Atomic, focused functions with clear error contexts
⏰ 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 (x86_64-pc-windows-msvc, amd64, x64, windows-x86_64) / Build windows artefacts
  • GitHub Check: release / build-windows (aarch64-pc-windows-msvc, arm64, arm64, windows-aarch64) / Build windows artefacts
  • GitHub Check: release / build-linux (aarch64-unknown-linux-gnu, linux-arm64, arm64) / Build linux artefacts
  • GitHub Check: Sourcery review
  • GitHub Check: build-test (ubuntu-latest, stable)

Comment thread test_support/src/check_ninja.rs
@coderabbitai
Copy link
Copy Markdown
Contributor

coderabbitai Bot commented Dec 12, 2025

Tip

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

🧩 Analysis chain

🏁 Script executed:

#!/bin/bash
# Read the current state of test_support/src/check_ninja.rs to analyze string parameters
cat test_support/src/check_ninja.rs

Length of output: 10772


@leynos: I've analysed the module. The ToolName newtype has already been successfully introduced. With 40.7% vs. a 39.0% threshold, only marginal reduction is needed. The remaining string-heavy parameter is expected_directory: Option<&str>, which represents a domain concept (working directory path).

Analysis

String parameters by category:

  • Domain concept: expected_directory: Option<&str> (3 occurrences across public API and internals)
  • Error contexts: context: &str (5 occurrences) — acceptable, internal error messages
  • Shell script internals: var_name, flag, expected_value — implementation details for script generation

Suggested Fix

The most idiomatic Rust approach is to use &Path for filesystem paths rather than &str. This has several benefits:

  • More type-safe (paths are not arbitrary strings)
  • Idiomatic Rust (standard library path APIs expect Path)
  • Reduces string percentage below threshold
  • No new types needed—uses stdlib

Update the signatures to accept Option<&Path> and convert to string internally:

/// Builds the shell script content for validating ninja tool invocation.
#[cfg(unix)]
fn build_tool_validation_script(
    expected_tool: ToolName,
    expected_jobs: Option<u32>,
    expected_directory: Option<&Path>,
) -> String {
    let jobs = expected_jobs
        .map(|j| FlagValidation::new("-j", "jobs", &j.to_string()))
        .unwrap_or_else(FlagValidation::empty);
    let dir = expected_directory
        .and_then(|p| p.to_str())
        .map(|d| FlagValidation::new("-C", "dir", d))
        .unwrap_or_else(FlagValidation::empty);

    TOOL_VALIDATION_TEMPLATE
        .replace("{expected}", expected_tool.as_str())
        .replace("{flag_inits}", &format!("{}{}", jobs.init, dir.init))
        .replace(
            "{flag_loop_checks}",
            &format!("{}{}", jobs.loop_check, dir.loop_check),
        )
        .replace(
            "{flag_validations}",
            &format!("{}{}", jobs.validation, dir.validation),
        )
}

/// Create a fake Ninja that validates `-t <tool>` and optionally `-j <jobs>` and `-C <dir>`.
///
/// The script scans command-line arguments for `-t <tool>`, `-f <file>`, and
/// optionally `-j <jobs>` and `-C <directory>`. It exits with status `0` if all
/// expected arguments are found, otherwise `1`.
///
/// # Arguments
///
/// * `expected_tool` - The tool name that should follow `-t` (e.g., `"clean"`)
/// * `expected_jobs` - Optional job count that should follow `-j`
/// * `expected_directory` - Optional working directory path that should follow `-C`
///
/// # Example
///
/// ```rust,ignore
/// use test_support::check_ninja::{fake_ninja_expect_tool_with_jobs, ToolName};
/// use std::path::Path;
///
/// let (dir, ninja_path) = fake_ninja_expect_tool_with_jobs(
///     ToolName::new("clean"),
///     Some(4),
///     Some(Path::new("/path/to/build")),
/// )?;
/// // ninja_path will succeed only when invoked with `-t clean -j 4 -C /path/to/build`
/// ```
#[cfg(unix)]
pub fn fake_ninja_expect_tool_with_jobs(
    expected_tool: ToolName,
    expected_jobs: Option<u32>,
    expected_directory: Option<&Path>,
) -> Result<(TempDir, PathBuf)> {
    let dir = TempDir::new().context("fake_ninja_expect_tool_with_jobs: create temp dir")?;
    let path = dir.path().join("ninja");
    let mut file = File::create(&path).with_context(|| {
        format!(
            "fake_ninja_expect_tool_with_jobs: create script {}",
            path.display()
        )
    })?;
    let script_content =
        build_tool_validation_script(expected_tool, expected_jobs, expected_directory);
    write!(file, "{}", script_content).with_context(|| {
        format!(
            "fake_ninja_expect_tool_with_jobs: write script {}",
            path.display()
        )
    })?;
    make_script_executable(&path, "fake_ninja_expect_tool_with_jobs")?;
    Ok((dir, path))
}

/// Stub for non-Unix platforms that returns an error.
#[cfg(not(unix))]
pub fn fake_ninja_expect_tool_with_jobs(
    _expected_tool: ToolName,
    _expected_jobs: Option<u32>,
    _expected_directory: Option<&Path>,
) -> Result<(TempDir, PathBuf)> {
    anyhow::bail!("fake_ninja_expect_tool_with_jobs is only supported on Unix platforms")
}

Update the test in the same file:

let (dir, ninja_path) =
    fake_ninja_expect_tool_with_jobs(ToolName::new("clean"), None, Some(Path::new("/path/to/build")))?;

This reduces string parameters from 15 to 12 out of ~27 total parameters, bringing the ratio to ~44.4% → wait, that's still too high. Let me recalculate... Actually, the issue states the module has gone from having string parameters to 40.7%, so we need minimal reduction. Converting 3 &str params to &Path should be sufficient.

AI Coding Agent Prompt

Reduce string-heavy function arguments in `test_support/src/check_ninja.rs` by converting directory path parameters from `&str` to `&Path`:

1. Update `build_tool_validation_script` function signature (around line 205):
   - Change parameter from `expected_directory: Option<&str>` to `expected_directory: Option<&Path>`
   - Update the dir variable creation (around line 211) to:
     ```rust
     let dir = expected_directory
         .and_then(|p| p.to_str())
         .map(|d| FlagValidation::new("-C", "dir", d))
         .unwrap_or_else(FlagValidation::empty);
     ```

2. Update `fake_ninja_expect_tool_with_jobs` function signature (around line 252):
   - Change parameter from `expected_directory: Option<&str>` to `expected_directory: Option<&Path>`
   - Update the doc comment example (around line 247) to include `use std::path::Path;` and change `Some("/path/to/build")` to `Some(Path::new("/path/to/build"))`

3. Update the non-Unix stub `fake_ninja_expect_tool_with_jobs` (around line 278):
   - Change parameter from `_expected_directory: Option<&str>` to `_expected_directory: Option<&Path>`

4. Update the test `fake_ninja_validates_directory_flag` (around line 295):
   - Change the function call from `fake_ninja_expect_tool_with_jobs(ToolName::new("clean"), None, Some("/path/to/build"))` to `fake_ninja_expect_tool_with_jobs(ToolName::new("clean"), None, Some(Path::new("/path/to/build")))`

5. Search the entire codebase for other call sites to `fake_ninja_expect_tool_with_jobs`:
   - Use `rg 'fake_ninja_expect_tool_with_jobs'` to find all usages
   - Update any call sites passing `Some("string")` to use `Some(Path::new("string"))` instead

Ensure all tests pass with `cargo test`.

…ests

Refactor the `fake_ninja_expect_tool_with_jobs` function and related calls to accept `Option<&Path>` instead of `Option<&str>` for the expected directory argument. This provides better type safety and consistency.

Additionally, the `fake_ninja_validates_directory_flag` test was updated to use parameterized test cases via `rstest` to cover multiple scenarios of the `-C` flag more succinctly and clearly.

Co-authored-by: terragon-labs[bot] <terragon-labs[bot]@users.noreply.github.com>
codescene-delta-analysis[bot]

This comment was marked as outdated.

@leynos
Copy link
Copy Markdown
Owner Author

leynos commented Dec 12, 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:

String Heavy Function Arguments

test_support/src/check_ninja.rs:

What lead to degradation?

In this module, 40.7% of all arguments to its 16 functions are strings. The threshold for string arguments is 39.0%

Why does this problem occur?

String is a generic type that fail to capture the constraints of the domain object it represents. In this module, 41 % of all function arguments are string types.

How to fix it?

Heavy string usage indicates a missing domain language. Introduce data types that encapsulate the semantics. For example, a user_name is better represented as a constrained User type rather than a pure string, which could be anything.

@coderabbitai

This comment was marked as resolved.

…t generation

- Introduced ShellFlag struct to encapsulate Ninja flags and variable names.
- Combined separate functions into a single build_flag_validation returning init, loop_check, validation snippets.
- Removed FlagValidation struct in favor of tuple returns, simplifying code.
- Updated script building code to use new abstractions for jobs and directory flags.

This improves code clarity and reduces duplication in shell script validations.

Co-authored-by: terragon-labs[bot] <terragon-labs[bot]@users.noreply.github.com>
Copy link
Copy Markdown
Contributor

@coderabbitai coderabbitai Bot left a comment

Choose a reason for hiding this comment

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

Actionable comments posted: 1

Caution

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

⚠️ Outside diff range comments (1)
test_support/src/check_ninja.rs (1)

91-100: Fix broken error output in fake_ninja_check_build_file script.

Print $2 with double quotes so the argument interpolates; the current single-quoted string prints a literal $2.

-            "  echo 'missing build file: $2' >&2\n",
+            "  echo \"missing build file: $2\" >&2\n",
📜 Review details

Configuration used: CodeRabbit UI

Review profile: ASSERTIVE

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between 0afdac7 and d1a9b4b.

📒 Files selected for processing (1)
  • test_support/src/check_ninja.rs (2 hunks)
🧰 Additional context used
📓 Path-based instructions (1)
**/*.rs

📄 CodeRabbit inference engine (AGENTS.md)

**/*.rs: Clippy warnings MUST be disallowed in Rust code.
Fix any warnings emitted during Rust tests in the code itself rather than silencing them.
In Rust, extract meaningfully named helper functions when a function is too long, adhering to separation of concerns and CQRS.
In Rust, group related parameters in meaningfully named structs when a function has too many parameters.
In Rust, consider using Arc to reduce data returned when a function is returning a large error.
Every Rust module must begin with a module level (//!) comment explaining the module's purpose and utility.
Document public APIs in Rust using Rustdoc comments (///) so documentation can be generated with cargo doc.
In Rust, prefer immutable data and avoid unnecessary mut bindings.
In Rust, handle errors with the Result type instead of panicking where feasible.
Avoid unsafe code in Rust unless absolutely necessary, and document any usage clearly.
In Rust, place function attributes after doc comments.
In Rust, do not use return in single-line functions.
In Rust, use predicate functions for conditional criteria with more than two branches.
Lints in Rust must not be silenced except as a last resort.
Lint rule suppressions in Rust must be tightly scoped and include a clear reason.
In Rust, prefer expect over allow.
In Rust tests, use rstest fixtures for shared setup.
In Rust tests, replace duplicated tests with #[rstest(...)] parameterized cases.
In Rust, prefer mockall for mocks and stubs.
In Rust, use concat!() to combine long string literals rather than escaping newlines with a backslash.
In Rust, prefer single line versions of functions where appropriate (e.g., pub fn new(id: u64) -> Self { Self(id) } instead of multi-line).
Use NewTypes in Rust to model domain values and eliminate 'integer soup'. Reach for newt-hype when introducing many homogeneous wrappers; add small shims for string-backed wrappers. For path-centric wrappers implement AsRef<Path> along...

Files:

  • test_support/src/check_ninja.rs

⚙️ CodeRabbit configuration file

**/*.rs: * Seek to keep the cognitive complexity of functions no more than 9.

  • 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() in tests.
  • .expect() and .unwrap() are forbidden outside of tests. Errors must be propagated.
  • 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...

Files:

  • test_support/src/check_ninja.rs
🔍 Remote MCP Deepwiki, Ref

Summary of additional relevant facts for reviewing PR #247 ("Implement clean subcommand"):

  • New public API: run_ninja_tool(program, cli, build_file, tool) added; runner now re-exports run_ninja and run_ninja_tool (affects public surface and tests).,

  • Process refactor: configure_ninja_base + two configurators (configure_ninja_build_command, configure_ninja_tool_command) and centralized run_command_and_stream; run_ninja now uses build configurator and run_ninja_tool uses tool configurator.

  • Clean implementation: runner.handle_clean generates Ninja manifest (temporary or emitted), writes it, and invokes Ninja with -t clean via run_ninja_tool; uses same manifest-generation pipeline as build.

  • Tests and test-support:

    • test_support: introduces ToolName newtype and Unix-only fake_ninja_expect_tool* helpers that create shell-script fake Ninja validating -t, -f, optional -j and -C flags; non-Unix stubs present.
    • tests: new BDD feature tests (tests/features/clean.feature), step defs (tests/steps/process_steps.rs), and unit/integration tests (tests/runner_tests.rs) cover success, failing ninja exit, missing ninja binary, invalid manifest, and respect for -C and -j. Many tests are Unix-only guarded.
  • Manifest/test fixture change: test_support/src/env.rs changed embedded manifest structure (removed nested recipe block; promoted command field) — may affect tests that assert manifest text.

  • Security / platform notes: fake-Ninja helpers produce POSIX sh scripts and are appropriately #[cfg(unix)]-guarded; Windows stubs exist but do not validate tool invocation. Reviewers previously requested and confirmed these platform guards.

  • Documentation: docs/roadmap.md updated to mark the "clean" subcommand implemented.

Files of interest to review closely: src/runner/mod.rs, src/runner/process/mod.rs, test_support/src/check_ninja.rs, test_support/src/env.rs, tests/features/clean.feature, tests/runner_tests.rs, tests/steps/process_steps.rs, docs/roadmap.md.

⏰ 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-windows (x86_64-pc-windows-msvc, amd64, x64, windows-x86_64) / Build windows artefacts
  • GitHub Check: release / build-windows (aarch64-pc-windows-msvc, arm64, arm64, windows-aarch64) / Build windows artefacts
  • GitHub Check: release / build-linux (aarch64-unknown-linux-gnu, linux-arm64, arm64) / Build linux artefacts
  • GitHub Check: release / build-macos (x86_64-apple-darwin, macos-15-intel, macos-x86_64, macos-x86_64) / Build macos artefacts
  • GitHub Check: Sourcery review
  • GitHub Check: build-test (ubuntu-latest, stable)
🔇 Additional comments (2)
test_support/src/check_ninja.rs (2)

9-29: Keep ToolName as-is; it enforces the domain concept and reduces string soup.


285-318: Keep the #[rstest] parameterisation; it removes duplication and tightens coverage.

Comment thread test_support/src/check_ninja.rs
Add a helper to safely shell-quote strings for insertion into shell scripts
used in fake ninja tests. This prevents issues with unescaped values causing
wrong shell script behavior or security risks. Update the tool validation
scripts to use this quoting when embedding expected tool names and directory
paths.

Co-authored-by: terragon-labs[bot] <terragon-labs[bot]@users.noreply.github.com>
@leynos leynos changed the title Implement clean subcommand with tests Implement clean subcommand with tests; secure shell literals and UTF8 dir checks Dec 12, 2025
@leynos leynos merged commit b9b84ec into main Dec 12, 2025
15 checks passed
@leynos leynos deleted the terragon/implement-clean-subcommand-qo6gzi branch December 12, 2025 02:49
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant