Skip to content

Add verbose logging via tracing#45

Merged
leynos merged 5 commits intomainfrom
codex/add-verbose-logging-argument
Aug 5, 2025
Merged

Add verbose logging via tracing#45
leynos merged 5 commits intomainfrom
codex/add-verbose-logging-argument

Conversation

@leynos
Copy link
Owner

@leynos leynos commented Aug 5, 2025

Summary

  • add a --verbose flag to emit debugging logs
  • log parsed Netsukefile AST, generated Ninja file path, and the Ninja invocation command
  • document and test verbose mode

Testing

  • make lint
  • make test
  • make markdownlint
  • make nixie (fails: FileNotFound: failed copying files from cache to destination for package highlight.js)

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

Summary by Sourcery

Add a verbose mode controlled by a CLI flag to emit debug-level logs of the manifest AST, Ninja file generation, and Ninja invocation, and update code, tests, docs, and dependencies accordingly.

New Features:

  • Introduce --verbose flag for CLI to enable debug logging via tracing
  • Log parsed Netsuke manifest AST, generated Ninja file path, and the Ninja command invocation

Enhancements:

  • Derive Serialize for AST structs and add serde_json pretty-print support

Build:

  • Add tracing, tracing-subscriber, and serde_json dependencies to Cargo.toml

Documentation:

  • Document the verbose flag in the CLI design docs and README

Tests:

  • Extend CLI tests to verify verbose flag parsing and add an integration test for Ninja file creation

Introduce a --verbose flag that enables tracing-based diagnostics. The flag prints the parsed manifest AST, the generated Ninja file path, and the command used to invoke Ninja. Update tests and documentation.
@sourcery-ai
Copy link
Contributor

sourcery-ai bot commented Aug 5, 2025

Reviewer's Guide

Introduces a --verbose CLI flag that initializes a tracing subscriber and emits structured debug/info logs (including the parsed AST JSON, generated Ninja file path, and invocation command), extends AST types with serialization support, and updates tests, documentation, and dependencies accordingly.

Sequence diagram for verbose logging initialization and output

sequenceDiagram
    actor User
    participant CLI as Cli
    participant Main as main()
    participant Tracing as tracing_subscriber
    participant Runner as runner::run
    User->>CLI: Passes --verbose flag
    CLI->>Main: Parse CLI args
    Main->>Tracing: Initialize tracing_subscriber if verbose
    Main->>Runner: Call runner::run(&cli)
    Runner->>Tracing: Emit debug/info logs (AST, Ninja file path, command)
    Runner-->>Main: Result
    Main-->>User: Output (with verbose logs if enabled)
Loading

Updated class diagram for AST types with Serialize support

classDiagram
    class NetsukeManifest {
        +semver::Version netsuke_version
        +HashMap<String, Rule> rules
        +Vec<Target> targets
        +implements Serialize
    }
    class Rule {
        +String name
        +Option<String> description
        +Recipe recipe
        +implements Serialize
    }
    class Recipe {
        <<enum>>
        +Shell(String)
        +Script(String)
        +Rule(String)
        +implements Serialize
    }
    class Target {
        +StringOrList outputs
        +StringOrList deps
        +Option<String> rule
        +Option<String> description
        +Option<bool> phony
        +Option<bool> always
        +implements Serialize
    }
    class StringOrList {
        <<enum>>
        +String
        +Vec<String>
        +implements Serialize
    }
    NetsukeManifest "1" -- "*" Rule : rules
    NetsukeManifest "1" -- "*" Target : targets
    Rule "1" -- "1" Recipe : recipe
    Target "1" -- "1" StringOrList : outputs
    Target "1" -- "1" StringOrList : deps
Loading

Class diagram for CLI struct with new verbose flag

classDiagram
    class Cli {
        +PathBuf file
        +Option<PathBuf> directory
        +Option<usize> jobs
        +bool verbose
        +Option<Commands> command
    }
Loading

File-Level Changes

Change Details Files
Introduce verbose CLI option and initialize tracing subscriber
  • Add verbose boolean field to the CLI struct with the appropriate argument annotation
  • Modify main to invoke tracing_subscriber setup at DEBUG level when verbose is true
src/cli.rs
src/main.rs
Emit structured logging in the runner
  • Serialize manifest to pretty JSON and emit it via debug!
  • Write generated Ninja content to a file and log its path with info!
  • Capture the Ninja program and its arguments, then log the invocation command with info!
src/runner.rs
Enable AST serialization for JSON logging
  • Derive Serialize on manifest, rule, recipe, target, and helper types
  • Import serde::Serialize alongside existing deserialization imports
src/ast.rs
Add logging and JSON dependencies
  • Add tracing and tracing-subscriber crates for structured logging
  • Include serde_json crate for AST JSON serialization
Cargo.toml
Extend tests to cover verbose mode and file generation
  • Update CLI parsing tests to assert the verbose flag behavior
  • Add an integration test to verify that build.ninja is written correctly
  • Ensure existing runner tests account for the default verbose: false field
tests/cli_tests.rs
tests/runner_tests.rs
Document verbose logging in README and design docs
  • Add -v/--verbose flag description in the README usage section
  • Describe the new verbose option and logging rationale in the design document
README.md
docs/netsuke-design.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
Contributor

coderabbitai bot commented Aug 5, 2025

Note

Other AI code review bot(s) detected

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

Summary by CodeRabbit

  • New Features

    • Added a verbose logging mode, enabled via the -v or --verbose flag in the CLI, providing detailed diagnostic output.
    • Introduced redaction of sensitive information (such as passwords and tokens) in command-line logs for improved security.
    • Enabled serialisation of build manifest structures to JSON.
  • Documentation

    • Updated CLI usage in the README and technical design documentation to reflect the new verbose logging option and logging framework.
  • Bug Fixes

    • Sensitive command-line arguments are now redacted in logs to prevent accidental exposure.
  • Tests

    • Added and updated tests to verify verbose mode behaviour, log output, and redaction of sensitive information.
    • Enhanced test utilities to support log capturing for assertions.

Walkthrough

Add verbose logging support to the Netsuke CLI and build system. Extend the CLI struct and command-line parsing with a --verbose flag, update documentation, and integrate structured logging using the tracing and tracing-subscriber crates. Enhance the runner to emit debug and info output during manifest processing and Ninja invocation. Update tests accordingly.

Changes

Cohort / File(s) Change Summary
Workflow Verbosity
.github/workflows/netsukefile-test.yml
Add --verbose flag to the Netsuke build command in the workflow step for verbose output during CI builds.
Dependency and Logging Support
Cargo.toml
Add tracing, tracing-subscriber (with "fmt" feature), and serde_json as dependencies to enable structured logging and JSON serialisation.
CLI Documentation
README.md, docs/netsuke-design.md
Document the new -v/--verbose CLI option and update the technology summary to mention tracing for logging. Describe the new verbose boolean field in the CLI struct.
CLI Struct and Argument Parsing
src/cli.rs, docs/netsuke-design.md
Add verbose: bool field to the Cli struct, annotated for short and long CLI flags. Update documentation to reflect this addition.
Verbose Logging Initialisation
src/main.rs
Initialise a tracing subscriber at DEBUG level if the verbose flag is set in CLI args before running the main logic.
AST Serialisation
src/ast.rs
Derive Serialize for NetsukeManifest, Rule, Target, and StringOrList to enable JSON serialisation for debug logging.
Runner Logging and Manifest Handling
src/runner.rs
Add manifest reading, parsing, pretty-printing as JSON for debug logging, and build graph generation before Ninja invocation. Log the Ninja command at info level before execution. Implement argument redaction for sensitive data in logged commands. No changes to process spawning or output handling.
CLI and Runner Testing
tests/cli_tests.rs, tests/runner_tests.rs
Update CLI tests to handle the new verbose parameter. Add test for --verbose flag. Add runner tests to verify build.ninja file creation, command logging with redaction, and verbose mode log emission. Set verbose field in test CLI struct construction.
Test Support for Log Capture
tests/support/mod.rs
Add BufferWriter and capture_logs function to capture and return logs emitted during test execution using tracing_subscriber.

Sequence Diagram(s)

sequenceDiagram
    participant User
    participant CLI
    participant Main
    participant Runner
    participant Tracing
    participant Ninja

    User->>CLI: Invoke with --verbose flag
    CLI->>Main: Parse args (verbose: true)
    Main->>Tracing: Initialise logging at DEBUG if verbose
    Main->>Runner: Run command (with verbose)
    Runner->>Tracing: Log manifest as JSON (debug)
    Runner->>Tracing: Log Ninja command (info)
    Runner->>Ninja: Execute build
    Ninja-->>Runner: Return result
    Runner-->>Main: Return status
    Main-->>User: Exit with result
Loading

Estimated code review effort

🎯 3 (Moderate) | ⏱️ ~20 minutes

Possibly related PRs

  • Implement initial CLI #20: Implements the initial CLI structure and command dispatch, which is directly extended and modified by this PR to add verbose logging and enhanced runner logic.
  • Mark AST deserialisation done #25: Adds Deserialize trait derivations and tests for AST structs, complementing this PR's addition of Serialize trait derivations.
  • Invoke ninja subprocess #41: Introduces the initial Ninja subprocess invocation and runner functions extended here with verbose logging and argument redaction.

Poem

Logs now burst in vivid streams,
With tracing crates and JSON dreams.
The CLI speaks—verbose and clear,
While Ninja builds, the logs appear.
Tests ensure it all aligns,
In Netsuke’s code, the future shines!
🐭✨

✨ Finishing Touches
  • 📝 Generate Docstrings
🧪 Generate unit tests
  • Create PR with unit tests
  • Post copyable unit tests in a comment
  • Commit unit tests in branch codex/add-verbose-logging-argument

🪧 Tips

Chat

There are 3 ways to chat with CodeRabbit:

  • Review comments: Directly reply to a review comment made by CodeRabbit. Example:
    • I pushed a fix in commit <commit_id>, please review it.
    • Explain this complex logic.
    • Open a follow-up GitHub issue for this discussion.
  • Files and specific lines of code (under the "Files changed" tab): Tag @coderabbitai in a new review comment at the desired location with your query. Examples:
    • @coderabbitai explain this code block.
  • PR comments: Tag @coderabbitai in a new PR comment to ask questions about the PR branch. For the best results, please provide a very specific query, as very limited context is provided in this mode. Examples:
    • @coderabbitai gather interesting stats about this repository and render them as a table. Additionally, render a pie chart showing the language distribution in the codebase.
    • @coderabbitai read src/utils.ts and explain its main purpose.
    • @coderabbitai read the files in the src/scheduler package and generate a class diagram using mermaid and a README in the markdown format.

Support

Need help? Create a ticket on our support page for assistance with any issues or questions.

CodeRabbit Commands (Invoked using PR comments)

  • @coderabbitai pause to pause the reviews on a PR.
  • @coderabbitai resume to resume the paused reviews.
  • @coderabbitai review to trigger an incremental review. This is useful when automatic reviews are disabled for the repository.
  • @coderabbitai full review to do a full review from scratch and review all the files again.
  • @coderabbitai summary to regenerate the summary of the PR.
  • @coderabbitai generate docstrings to generate docstrings for this PR.
  • @coderabbitai generate sequence diagram to generate a sequence diagram of the changes in this PR.
  • @coderabbitai generate unit tests to generate unit tests for this PR.
  • @coderabbitai resolve resolve all the CodeRabbit review comments.
  • @coderabbitai configuration to show the current CodeRabbit configuration for the repository.
  • @coderabbitai help to get help.

Other keywords and placeholders

  • Add @coderabbitai ignore anywhere in the PR description to prevent this PR from being reviewed.
  • Add @coderabbitai summary to generate the high-level summary at a specific location in the PR description.
  • Add @coderabbitai anywhere in the PR title to generate the title automatically.

CodeRabbit Configuration File (.coderabbit.yaml)

  • You can programmatically configure CodeRabbit by adding a .coderabbit.yaml file to the root of your repository.
  • Please see the configuration documentation for more information.
  • If your editor has YAML language server enabled, you can add the path at the top of this file to enable auto-completion and validation: # yaml-language-server: $schema=https://coderabbit.ai/integrations/schema.v2.json

Documentation and Community

  • Visit our Documentation for detailed information on how to use CodeRabbit.
  • Join our Discord Community to get help, request features, and share feedback.
  • Follow us on X/Twitter for updates and announcements.

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.

Hey @leynos - I've reviewed your changes and found some issues that need to be addressed.

Blocking issues:

  • Add behavioural and unit tests for the new manifest loading and Ninja file generation logic. (link)

General comments:

  • Consider using a tracing_subscriber with an env filter (e.g. .with_env_filter) so you can flexibly control info vs debug log levels rather than only toggling all logging via --verbose.
  • Switch .init() to .try_init() or otherwise guard against multiple initializations of the global subscriber to avoid panics when running tests or embedding in other libraries.
  • Deriving Serialize on all AST types bloats the binary; consider logging a trimmed-down or temporary JSON representation instead of exposing full serialization on production structs.
Prompt for AI Agents
Please address the comments from this code review:
## Overall Comments
- Consider using a tracing_subscriber with an env filter (e.g. `.with_env_filter`) so you can flexibly control info vs debug log levels rather than only toggling all logging via `--verbose`.
- Switch `.init()` to `.try_init()` or otherwise guard against multiple initializations of the global subscriber to avoid panics when running tests or embedding in other libraries.
- Deriving Serialize on all AST types bloats the binary; consider logging a trimmed-down or temporary JSON representation instead of exposing full serialization on production structs.

## Individual Comments

### Comment 1
<location> `src/runner.rs:82` </location>
<code_context>
     cmd.stdout(Stdio::piped());
     cmd.stderr(Stdio::piped());

+    let program = cmd.get_program().to_string_lossy().into_owned();
+    let args: Vec<String> = cmd
+        .get_args()
+        .map(|a| a.to_string_lossy().into_owned())
+        .collect();
+    info!("Running command: {} {}", program, args.join(" "));
+
     let mut child = cmd.spawn()?;
</code_context>

<issue_to_address>
Logging the full command line may expose sensitive data.

Redact or omit sensitive arguments from logs to prevent exposure of confidential data.
</issue_to_address>

<suggested_fix>
<<<<<<< SEARCH
    let program = cmd.get_program().to_string_lossy().into_owned();
    let args: Vec<String> = cmd
        .get_args()
        .map(|a| a.to_string_lossy().into_owned())
        .collect();
    info!("Running command: {} {}", program, args.join(" "));
=======
    let program = cmd.get_program().to_string_lossy().into_owned();
    let args: Vec<String> = cmd
        .get_args()
        .map(|a| a.to_string_lossy().into_owned())
        .collect();

    // Redact sensitive arguments
    let redacted_args: Vec<String> = args
        .iter()
        .map(|arg| {
            let lower = arg.to_lowercase();
            if lower.contains("password")
                || lower.contains("token")
                || lower.contains("secret")
            {
                // Redact the value if it's in the form --key=value or just the value
                if let Some(idx) = arg.find('=') {
                    format!("{}=***REDACTED***", &arg[..idx])
                } else {
                    "***REDACTED***".to_string()
                }
            } else {
                arg.clone()
            }
        })
        .collect();

    info!("Running command: {} {}", program, redacted_args.join(" "));
>>>>>>> REPLACE

</suggested_fix>

### Comment 2
<location> `tests/runner_tests.rs:14` </location>
<code_context>
         file: PathBuf::from("Netsukefile"),
         directory: None,
         jobs: None,
+        verbose: false,
         command: Some(Commands::Build {
             targets: Vec::new(),
</code_context>

<issue_to_address>
Missing test for runner with verbose mode enabled.

Please add a test with `verbose` set to true and verify the runner's behavior, such as checking for expected log output or logging initialization.

Suggested implementation:

```rust
        file: PathBuf::from("Netsukefile"),
        directory: None,
        jobs: None,
        verbose: false,
        command: Some(Commands::Build {
            targets: Vec::new(),
        }),
        runner::run_ninja(Path::new("does-not-exist"), &cli, &[]).expect_err("process should fail");
    assert_eq!(err.kind(), std::io::ErrorKind::NotFound);
}

#[rstest]
fn run_with_verbose_mode_emits_logs() {
    use std::io::Cursor;
    use std::sync::{Arc, Mutex};
    use std::path::Path;

    // Setup a CLI with verbose enabled
    let cli = Cli {
        file: PathBuf::from("Netsukefile"),
        directory: None,
        jobs: None,
        verbose: true,
        command: Some(Commands::Build {
            targets: Vec::new(),
        }),
    };

    // Optionally, capture stdout/stderr if runner writes logs there
    // Here, we use a simple approach; adapt as needed for your logging setup
    let result = runner::run_ninja(Path::new("does-not-exist"), &cli, &[]);
    assert!(result.is_err());

    // If your runner logs to stdout/stderr, you may want to capture and assert log output here.
    // For example, if using the `log` crate with a test logger, assert that a verbose log was emitted.
    // This is a placeholder assertion; replace with actual log capture if available.
    // assert!(captured_logs.contains("verbose") || captured_logs.contains("DEBUG"));
}

```

- If your project uses a custom logger or test logger, you may need to set up log capturing and assert that the expected verbose log messages are present.
- Replace the placeholder assertion with actual log output checks if your runner emits logs to a specific sink.
- Ensure the `Cli` struct and `runner::run_ninja` are in scope and imported as needed.
</issue_to_address>

### Comment 3
<location> `src/runner.rs:28` </location>
<code_context>
     });
     match command {
-        Commands::Build { targets } => run_ninja(Path::new("ninja"), cli, &targets),
+        Commands::Build { targets } => {
+            let manifest_path = cli
+                .directory
</code_context>

<issue_to_address>
Consider extracting the steps in the Build command into separate helper functions to simplify the run function.

```markdown
You can flatten out the `Build` arm by extracting the key steps into small, well-named helpers. For example:

```rust
pub fn run(cli: &Cli) -> io::Result<()> {
    match cli.command.clone().unwrap_or_default() {
        Commands::Build { targets } => handle_build(cli, &targets),
        Commands::Clean => { println!("Clean requested"); Ok(()) }
        Commands::Graph => { println!("Graph requested"); Ok(()) }
    }
}

fn handle_build(cli: &Cli, targets: &[String]) -> io::Result<()> {
    let manifest_path = resolve_manifest_path(cli);
    let manifest      = manifest::from_path(&manifest_path).map_err(io::Error::other)?;
    debug!("AST:\n{}", serde_json::to_string_pretty(&manifest).unwrap());

    let graph      = BuildGraph::from_manifest(&manifest).map_err(io::Error::other)?;
    let ninja_path = write_ninja_file(cli, &graph)?;
    info!("Generated Ninja file at {}", ninja_path.display());

    run_ninja(Path::new("ninja"), cli, targets)
}

fn resolve_manifest_path(cli: &Cli) -> PathBuf {
    cli.directory
       .as_ref()
       .map(|dir| dir.join(&cli.file))
       .unwrap_or_else(|| cli.file.clone())
}

fn write_ninja_file(cli: &Cli, graph: &BuildGraph) -> io::Result<PathBuf> {
    let content = ninja_gen::generate(graph);
    let path = cli
        .directory
        .as_ref()
        .map(|d| d.join("build.ninja"))
        .unwrap_or_else(|| PathBuf::from("build.ninja"));

    fs::write(&path, content)
        .map(|_| path)
        .map_err(io::Error::other)
}
```

This:

- Removes the inline `map_or_else` nesting,
- Consolidates each step (path resolution, manifest loading, file‐writing),
- Keeps `run` very shallow, and
- Preserves all existing functionality.
```
</issue_to_address>

### Comment 4
<location> `src/runner.rs:33` </location>
<code_context>
+                .directory
+                .as_ref()
+                .map_or_else(|| cli.file.clone(), |dir| dir.join(&cli.file));
+            let manifest = manifest::from_path(&manifest_path).map_err(io::Error::other)?;
+            let ast_json = serde_json::to_string_pretty(&manifest).map_err(io::Error::other)?;
+            debug!("AST:\n{ast_json}");
</code_context>

<issue_to_address>
Add behavioural and unit tests for the new manifest loading and Ninja file generation logic.

The logic for loading the manifest, serializing it, generating the build graph, and writing the Ninja file is new or significantly changed. Ensure that both behavioural (integration) and unit tests cover these code paths, including error handling and edge cases.
</issue_to_address>

### Comment 5
<location> `src/runner.rs:82` </location>
<code_context>
     cmd.stdout(Stdio::piped());
     cmd.stderr(Stdio::piped());

+    let program = cmd.get_program().to_string_lossy().into_owned();
+    let args: Vec<String> = cmd
+        .get_args()
</code_context>

<issue_to_address>
Add tests to verify that command logging occurs as expected when running Ninja.

The addition of logging the command and its arguments before execution is a new feature. Add tests to ensure that this logging occurs and is formatted correctly, especially in verbose mode.
</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.

@leynos
Copy link
Owner Author

leynos commented Aug 5, 2025

@coderabbitai review

@coderabbitai
Copy link
Contributor

coderabbitai bot commented Aug 5, 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
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 (2)
tests/runner_tests.rs (1)

41-72: Add test coverage for verbose mode functionality.

The integration test properly exercises manifest processing and Ninja file generation, but lacks specific coverage for verbose mode behaviour. Add a test that verifies verbose logging is enabled when the flag is set.

#[rstest]
fn run_with_verbose_mode() {
    let (ninja_dir, ninja_path) = support::fake_ninja(0);
    let original_path = std::env::var_os("PATH").unwrap_or_default();
    let mut paths: Vec<_> = std::env::split_paths(&original_path).collect();
    paths.insert(0, ninja_dir.path().to_path_buf());
    let new_path = std::env::join_paths(paths).expect("join paths");
    std::env::set_var("PATH", &new_path);

    let temp = tempfile::tempdir().expect("temp dir");
    let manifest_path = temp.path().join("Netsukefile");
    std::fs::copy("tests/data/minimal.yml", &manifest_path).expect("copy manifest");
    let cli = Cli {
        file: manifest_path,
        directory: Some(temp.path().to_path_buf()),
        jobs: None,
        verbose: true, // Test verbose mode
        command: Some(Commands::Build {
            targets: Vec::new(),
        }),
    };

    // Capture logs or verify verbose behavior
    let result = runner::run(&cli);
    assert!(result.is_ok());
    assert!(temp.path().join("build.ninja").exists());

    std::env::set_var("PATH", original_path);
    drop(ninja_path);
}
src/runner.rs (1)

82-87: Review command logging for potential sensitive data exposure.

The command logging provides good visibility into ninja execution, but logging the full command line may expose sensitive arguments like passwords or tokens.

Consider implementing argument redaction as suggested in past review comments to prevent exposure of sensitive data in logs.

📜 Review details

Configuration used: CodeRabbit UI
Review profile: ASSERTIVE
Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between 46d1a0b and 69ed1ff.

⛔ Files ignored due to path filters (1)
  • Cargo.lock is excluded by !**/*.lock
📒 Files selected for processing (10)
  • .github/workflows/netsukefile-test.yml (1 hunks)
  • Cargo.toml (1 hunks)
  • README.md (1 hunks)
  • docs/netsuke-design.md (2 hunks)
  • src/ast.rs (6 hunks)
  • src/cli.rs (1 hunks)
  • src/main.rs (1 hunks)
  • src/runner.rs (3 hunks)
  • tests/cli_tests.rs (1 hunks)
  • tests/runner_tests.rs (2 hunks)
🧰 Additional context used
📓 Path-based instructions (4)
**/*.md

📄 CodeRabbit Inference Engine (AGENTS.md)

**/*.md: Documentation must use en-GB-oxendict spelling and grammar, except for the naming of the "LICENSE" file.
Validate Markdown files using make markdownlint.
Run make fmt after any documentation changes to format all Markdown files and fix table markup.
Validate Mermaid diagrams in Markdown files by running make nixie.
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:

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

⚙️ CodeRabbit Configuration File

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

  • Use en-GB-oxendict (-ize / -our) spelling and grammar
  • Paragraphs and bullets must be wrapped to 80 columns, except where a long URL would prevent this (in which case, silence MD013 for that line)
  • Code blocks should be wrapped to 120 columns.
  • 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:

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

📄 CodeRabbit Inference Engine (AGENTS.md)

**/*.rs: Clippy warnings MUST be disallowed.
Fix any warnings emitted during tests in the code itself rather than silencing them.
Where a function is too long, extract meaningfully named helper functions adhering to separation of concerns and CQRS.
Where a function has too many parameters, group related parameters in meaningfully named structs.
Where a function is returning a large error consider using Arc to reduce the amount of data returned.
Write unit and behavioural tests for new functionality. Run both before and after making any change.
Every module must begin with a module level (//!) comment explaining the module's purpose and utility.
Document public APIs using Rustdoc comments (///) so documentation can be generated with cargo doc.
Prefer immutable data and avoid unnecessary mut bindings.
Handle errors with the Result type instead of panicking where feasible.
Avoid unsafe code unless absolutely necessary and document any usage clearly.
Place function attributes after doc comments.
Do not use return in single-line functions.
Use predicate functions for conditional criteria with more than two branches.
Lints must not be silenced except as a last resort.
Lint rule suppressions must be tightly scoped and include a clear reason.
Prefer expect over allow.
Prefer .expect() over .unwrap().
Use concat!() to combine long string literals rather than escaping newlines with a backslash.
Prefer semantic error enums: Derive std::error::Error (via the thiserror crate) for any condition the caller might inspect, retry, or map to an HTTP status.
Use an opaque error only at the app boundary: Use eyre::Report for human-readable logs; these should not be exposed in public APIs.
Never export the opaque type from a library: Convert to domain enums at API boundaries, and to eyre only in the main main() entrypoint or top-level async task.

Files:

  • src/cli.rs
  • src/main.rs
  • tests/cli_tests.rs
  • src/runner.rs
  • src/ast.rs
  • tests/runner_tests.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 / -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.
  • 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.

Files:

  • src/cli.rs
  • src/main.rs
  • tests/cli_tests.rs
  • src/runner.rs
  • src/ast.rs
  • tests/runner_tests.rs
Cargo.toml

📄 CodeRabbit Inference Engine (AGENTS.md)

Cargo.toml: Use explicit version ranges in Cargo.toml and keep dependencies up-to-date.
Mandate caret requirements for all dependencies: All crate versions specified in Cargo.toml must use SemVer-compatible caret requirements (e.g., some-crate = "1.2.3").
Prohibit unstable version specifiers: The use of wildcard (*) or open-ended inequality (>=) version requirements is strictly forbidden. Tilde requirements (~) should only be used where a dependency must be locked to patch-level updates for a specific, documented reason.

Files:

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

📄 CodeRabbit Inference Engine (AGENTS.md)

docs/**/*.md: Use the markdown files within the docs/ directory as a knowledge base and source of truth for project requirements, dependency choices, and architectural decisions.
Proactively update the relevant file(s) in the docs/ directory to reflect the latest state when new decisions are made, requirements change, libraries are added/removed, or architectural patterns evolve.

Files:

  • docs/netsuke-design.md
🧠 Learnings (1)
📚 Learning: applies to docs/src/manifest.rs : the manifest version field ('netsuke_version') must be validated u...
Learnt from: CR
PR: leynos/netsuke#0
File: docs/netsuke-design.md:0-0
Timestamp: 2025-08-03T23:02:36.801Z
Learning: Applies to docs/src/manifest.rs : The manifest version field ('netsuke_version') must be validated using the 'semver' crate to ensure it follows semantic versioning rules.

Applied to files:

  • src/ast.rs
🔇 Additional comments (13)
Cargo.toml (1)

16-18: Approve dependency versions: current and secure.

All three crates use caret requirements and align with the latest stable releases on crates.io:

  • tracing: ^0.1.0 (latest 0.1.41)
  • tracing-subscriber: ^0.3.0 (latest 0.3.19)
  • serde_json: ^1.0.0 (latest 1.0.142)

No relevant security advisories against these packages were found in the most recent Rust vulnerability feed.

src/cli.rs (1)

39-41: LGTM! Clean implementation of verbose flag.

The verbose flag follows established clap patterns and is properly documented. The implementation integrates well with the existing CLI structure.

src/ast.rs (2)

21-21: LGTM! Serialize trait addition supports debug logging.

Adding Serialize alongside existing Deserialize traits enables JSON serialization for the verbose logging functionality without affecting core parsing logic.


58-83: Confirm semver validation via serde
The netsuke_version field is of type semver::Version, and deriving Deserialize enforces correct semantic version parsing at deserialisation. No additional validation logic is required.

README.md (1)

161-161: LGTM! Clear documentation of verbose flag.

The verbose flag documentation follows the established format and provides clear information about enabling verbose logging output.

src/main.rs (1)

10-14: LGTM! Clean conditional logging initialization.

The verbose flag integration is clean and appropriate. DEBUG level logging provides good visibility for troubleshooting without overwhelming normal usage.

.github/workflows/netsukefile-test.yml (1)

40-40: LGTM!

Adding --verbose to the CI workflow properly exercises the new verbose logging functionality in automated tests.

docs/netsuke-design.md (2)

376-378: LGTM!

The CLI documentation correctly reflects the addition of the verbose boolean field for enabling debug-level logging.


1524-1524: LGTM!

The technology summary table appropriately includes the tracing crate for structured logging support.

tests/cli_tests.rs (1)

12-12: LGTM!

The CLI tests are properly updated to include the verbose parameter, maintaining backward compatibility for existing cases whilst adding coverage for the new --verbose flag functionality.

Also applies to: 18-18, 21-21, 27-27, 34-34

tests/runner_tests.rs (1)

14-14: LGTM!

The test helper correctly includes the verbose field set to false, maintaining consistency with the updated CLI structure.

src/runner.rs (2)

8-9: LGTM!

The additional imports are appropriate for the new functionality: BuildGraph and ninja_gen for build processing, serde_json for AST serialisation, and tracing for structured logging.

Also applies to: 12-12, 15-15


28-45: LGTM with complexity concern noted.

The Build command implementation correctly processes the manifest, generates debug output, creates the build graph, and writes the Ninja file. The verbose logging integration works well with the new tracing infrastructure.

Note that past review comments have identified this function as becoming complex and suggested extraction into helper functions.

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: 1

♻️ Duplicate comments (2)
src/runner.rs (2)

28-45: LGTM! Build command properly implements manifest processing pipeline.

The enhancement successfully implements the full build pipeline: manifest loading, AST serialization for debugging, build graph generation, and Ninja file creation. The structured logging provides good visibility into the process.

Note: The complexity concern raised in past reviews remains valid for future refactoring considerations.


82-87: LGTM! Command logging enhances debugging visibility.

The logging addition provides valuable insight into the exact command being executed, supporting the verbose logging objectives.

Note: The security concern about logging sensitive command-line arguments remains valid and should be addressed in future iterations.

📜 Review details

Configuration used: CodeRabbit UI
Review profile: ASSERTIVE
Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between 46d1a0b and 69ed1ff.

⛔ Files ignored due to path filters (1)
  • Cargo.lock is excluded by !**/*.lock
📒 Files selected for processing (10)
  • .github/workflows/netsukefile-test.yml (1 hunks)
  • Cargo.toml (1 hunks)
  • README.md (1 hunks)
  • docs/netsuke-design.md (2 hunks)
  • src/ast.rs (6 hunks)
  • src/cli.rs (1 hunks)
  • src/main.rs (1 hunks)
  • src/runner.rs (3 hunks)
  • tests/cli_tests.rs (1 hunks)
  • tests/runner_tests.rs (2 hunks)
🧰 Additional context used
📓 Path-based instructions (4)
**/*.rs

📄 CodeRabbit Inference Engine (AGENTS.md)

**/*.rs: Clippy warnings MUST be disallowed.
Fix any warnings emitted during tests in the code itself rather than silencing them.
Where a function is too long, extract meaningfully named helper functions adhering to separation of concerns and CQRS.
Where a function has too many parameters, group related parameters in meaningfully named structs.
Where a function is returning a large error consider using Arc to reduce the amount of data returned.
Write unit and behavioural tests for new functionality. Run both before and after making any change.
Every module must begin with a module level (//!) comment explaining the module's purpose and utility.
Document public APIs using Rustdoc comments (///) so documentation can be generated with cargo doc.
Prefer immutable data and avoid unnecessary mut bindings.
Handle errors with the Result type instead of panicking where feasible.
Avoid unsafe code unless absolutely necessary and document any usage clearly.
Place function attributes after doc comments.
Do not use return in single-line functions.
Use predicate functions for conditional criteria with more than two branches.
Lints must not be silenced except as a last resort.
Lint rule suppressions must be tightly scoped and include a clear reason.
Prefer expect over allow.
Prefer .expect() over .unwrap().
Use concat!() to combine long string literals rather than escaping newlines with a backslash.
Prefer semantic error enums: Derive std::error::Error (via the thiserror crate) for any condition the caller might inspect, retry, or map to an HTTP status.
Use an opaque error only at the app boundary: Use eyre::Report for human-readable logs; these should not be exposed in public APIs.
Never export the opaque type from a library: Convert to domain enums at API boundaries, and to eyre only in the main main() entrypoint or top-level async task.

Files:

  • src/main.rs
  • tests/cli_tests.rs
  • src/cli.rs
  • tests/runner_tests.rs
  • src/ast.rs
  • src/runner.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 / -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.
  • 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.

Files:

  • src/main.rs
  • tests/cli_tests.rs
  • src/cli.rs
  • tests/runner_tests.rs
  • src/ast.rs
  • src/runner.rs
docs/**/*.md

📄 CodeRabbit Inference Engine (AGENTS.md)

docs/**/*.md: Use the markdown files within the docs/ directory as a knowledge base and source of truth for project requirements, dependency choices, and architectural decisions.
Proactively update the relevant file(s) in the docs/ directory to reflect the latest state when new decisions are made, requirements change, libraries are added/removed, or architectural patterns evolve.

Files:

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

📄 CodeRabbit Inference Engine (AGENTS.md)

**/*.md: Documentation must use en-GB-oxendict spelling and grammar, except for the naming of the "LICENSE" file.
Validate Markdown files using make markdownlint.
Run make fmt after any documentation changes to format all Markdown files and fix table markup.
Validate Mermaid diagrams in Markdown files by running make nixie.
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/netsuke-design.md
  • README.md

⚙️ CodeRabbit Configuration File

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

  • Use en-GB-oxendict (-ize / -our) spelling and grammar
  • Paragraphs and bullets must be wrapped to 80 columns, except where a long URL would prevent this (in which case, silence MD013 for that line)
  • Code blocks should be wrapped to 120 columns.
  • 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
  • README.md
Cargo.toml

📄 CodeRabbit Inference Engine (AGENTS.md)

Cargo.toml: Use explicit version ranges in Cargo.toml and keep dependencies up-to-date.
Mandate caret requirements for all dependencies: All crate versions specified in Cargo.toml must use SemVer-compatible caret requirements (e.g., some-crate = "1.2.3").
Prohibit unstable version specifiers: The use of wildcard (*) or open-ended inequality (>=) version requirements is strictly forbidden. Tilde requirements (~) should only be used where a dependency must be locked to patch-level updates for a specific, documented reason.

Files:

  • Cargo.toml
🧠 Learnings (1)
📚 Learning: applies to docs/src/manifest.rs : the manifest version field ('netsuke_version') must be validated u...
Learnt from: CR
PR: leynos/netsuke#0
File: docs/netsuke-design.md:0-0
Timestamp: 2025-08-03T23:02:36.801Z
Learning: Applies to docs/src/manifest.rs : The manifest version field ('netsuke_version') must be validated using the 'semver' crate to ensure it follows semantic versioning rules.

Applied to files:

  • src/ast.rs
🔇 Additional comments (13)
src/cli.rs (1)

39-42: LGTM! Clean implementation of the verbose flag.

The verbose field follows established patterns in the CLI struct with proper clap annotations and clear documentation.

README.md (1)

161-161: Documentation update looks good.

The verbose flag documentation follows the established format and clearly describes the new functionality.

.github/workflows/netsukefile-test.yml (1)

40-40: Good addition to test verbose functionality in CI.

Adding the verbose flag to the workflow ensures the new logging functionality works correctly in the automated build environment.

src/main.rs (1)

10-14: Clean implementation of conditional verbose logging.

The tracing subscriber initialization is properly conditional and configured with appropriate DEBUG level logging for verbose output.

Cargo.toml (1)

16-18: Dependencies properly specified for logging functionality.

The new dependencies follow caret requirement guidelines and include appropriate features for the verbose logging implementation.

docs/netsuke-design.md (2)

376-378: LGTM! Documentation correctly reflects CLI verbose flag addition.

The documentation update accurately mirrors the CLI struct changes with proper attribute syntax and type specification.


1524-1524: LGTM! Technology table accurately documents tracing crate addition.

The description correctly captures tracing's role in providing structured diagnostic output for the verbose logging feature.

tests/cli_tests.rs (2)

12-21: LGTM! Test cases properly updated for verbose flag.

All existing test cases correctly include the new verbose parameter with appropriate default values, maintaining comprehensive coverage.


27-34: LGTM! Test function properly validates verbose flag parsing.

The function signature and assertion correctly handle the new verbose parameter, ensuring proper CLI parsing validation.

tests/runner_tests.rs (1)

14-14: LGTM! Test helper correctly includes verbose field.

The update maintains consistency with CLI struct changes. Note that verbose mode testing could be enhanced in future iterations.

src/ast.rs (2)

21-21: LGTM! Import correctly adds Serialize trait.

The import update properly supports the serialization functionality added throughout the module.


58-175: LGTM! Serialize derives enable debugging capabilities.

Adding Serialize to all major AST structures supports the verbose logging functionality where manifests are serialized to JSON for debug output. The changes maintain existing functionality while adding useful debugging capabilities.

src/runner.rs (1)

8-15: LGTM! Imports properly support enhanced runner functionality.

All new imports are necessary for the manifest processing, build graph generation, and structured logging capabilities.

Comment on lines +41 to +72
#[rstest]
fn run_writes_ninja_file() {
let (ninja_dir, ninja_path) = support::fake_ninja(0);
let original_path = std::env::var_os("PATH").unwrap_or_default();
let mut paths: Vec<_> = std::env::split_paths(&original_path).collect();
paths.insert(0, ninja_dir.path().to_path_buf());
let new_path = std::env::join_paths(paths).expect("join paths");
unsafe {
std::env::set_var("PATH", &new_path);
}

let temp = tempfile::tempdir().expect("temp dir");
let manifest_path = temp.path().join("Netsukefile");
std::fs::copy("tests/data/minimal.yml", &manifest_path).expect("copy manifest");
let cli = Cli {
file: manifest_path,
directory: Some(temp.path().to_path_buf()),
jobs: None,
verbose: false,
command: Some(Commands::Build {
targets: Vec::new(),
}),
};

runner::run(&cli).expect("run");
assert!(temp.path().join("build.ninja").exists());

unsafe {
std::env::set_var("PATH", original_path);
}
drop(ninja_path);
}
Copy link
Contributor

@coderabbitai coderabbitai bot Aug 5, 2025

Choose a reason for hiding this comment

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

🧹 Nitpick (assertive)

LGTM! Comprehensive test validates Ninja file generation.

The test thoroughly covers the new manifest processing and build file generation functionality.

Replace unsafe environment variable manipulation with a safer approach:

-    unsafe {
-        std::env::set_var("PATH", &new_path);
-    }
+    std::env::set_var("PATH", &new_path);
-    unsafe {
-        std::env::set_var("PATH", original_path);
-    }
+    std::env::set_var("PATH", original_path);

The set_var function is not unsafe and doesn't require unsafe blocks.

📝 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]
fn run_writes_ninja_file() {
let (ninja_dir, ninja_path) = support::fake_ninja(0);
let original_path = std::env::var_os("PATH").unwrap_or_default();
let mut paths: Vec<_> = std::env::split_paths(&original_path).collect();
paths.insert(0, ninja_dir.path().to_path_buf());
let new_path = std::env::join_paths(paths).expect("join paths");
unsafe {
std::env::set_var("PATH", &new_path);
}
let temp = tempfile::tempdir().expect("temp dir");
let manifest_path = temp.path().join("Netsukefile");
std::fs::copy("tests/data/minimal.yml", &manifest_path).expect("copy manifest");
let cli = Cli {
file: manifest_path,
directory: Some(temp.path().to_path_buf()),
jobs: None,
verbose: false,
command: Some(Commands::Build {
targets: Vec::new(),
}),
};
runner::run(&cli).expect("run");
assert!(temp.path().join("build.ninja").exists());
unsafe {
std::env::set_var("PATH", original_path);
}
drop(ninja_path);
}
#[rstest]
fn run_writes_ninja_file() {
let (ninja_dir, ninja_path) = support::fake_ninja(0);
let original_path = std::env::var_os("PATH").unwrap_or_default();
let mut paths: Vec<_> = std::env::split_paths(&original_path).collect();
paths.insert(0, ninja_dir.path().to_path_buf());
let new_path = std::env::join_paths(paths).expect("join paths");
std::env::set_var("PATH", &new_path);
let temp = tempfile::tempdir().expect("temp dir");
let manifest_path = temp.path().join("Netsukefile");
std::fs::copy("tests/data/minimal.yml", &manifest_path).expect("copy manifest");
let cli = Cli {
file: manifest_path,
directory: Some(temp.path().to_path_buf()),
jobs: None,
verbose: false,
command: Some(Commands::Build {
targets: Vec::new(),
}),
};
runner::run(&cli).expect("run");
assert!(temp.path().join("build.ninja").exists());
std::env::set_var("PATH", original_path);
drop(ninja_path);
}
🤖 Prompt for AI Agents
In tests/runner_tests.rs around lines 41 to 72, the test uses unsafe blocks to
call std::env::set_var for modifying the PATH environment variable, but set_var
is safe and does not require unsafe. Remove the unsafe blocks surrounding the
set_var calls to make the code safer and clearer.

Copy link
Owner Author

Choose a reason for hiding this comment

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

Has this now been resolved?

Copy link
Contributor

Choose a reason for hiding this comment

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

🧩 Analysis chain

@leynos No, the issue has not been resolved. The unsafe blocks are still present in the code.

Both test functions run_writes_ninja_file and run_with_verbose_mode_emits_logs still contain unnecessary unsafe blocks around std::env::set_var calls. The set_var function is safe and doesn't require unsafe blocks.

Here's the corrected code for both functions:

For run_writes_ninja_file:

#[rstest]
fn run_writes_ninja_file() {
    let (ninja_dir, ninja_path) = support::fake_ninja(0);
    let original_path = std::env::var_os("PATH").unwrap_or_default();
    let mut paths: Vec<_> = std::env::split_paths(&original_path).collect();
    paths.insert(0, ninja_dir.path().to_path_buf());
    let new_path = std::env::join_paths(paths).expect("join paths");
    std::env::set_var("PATH", &new_path);

    let temp = tempfile::tempdir().expect("temp dir");
    let manifest_path = temp.path().join("Netsukefile");
    std::fs::copy("tests/data/minimal.yml", &manifest_path).expect("copy manifest");
    let cli = Cli {
        file: manifest_path,
        directory: Some(temp.path().to_path_buf()),
        jobs: None,
        verbose: false,
        command: Some(Commands::Build {
            targets: Vec::new(),
        }),
    };

    runner::run(&cli).expect("run");
    assert!(temp.path().join("build.ninja").exists());

    std::env::set_var("PATH", original_path);
    drop(ninja_path);
}

For run_with_verbose_mode_emits_logs:

#[rstest]
fn run_with_verbose_mode_emits_logs() {
    let (ninja_dir, ninja_path) = support::fake_ninja(0);
    let original_path = std::env::var_os("PATH").unwrap_or_default();
    let mut paths: Vec<_> = std::env::split_paths(&original_path).collect();
    paths.insert(0, ninja_dir.path().to_path_buf());
    let new_path = std::env::join_paths(paths).expect("join paths");
    std::env::set_var("PATH", &new_path);

    let temp = tempfile::tempdir().expect("temp dir");
    let manifest_path = temp.path().join("Netsukefile");
    std::fs::copy("tests/data/minimal.yml", &manifest_path).expect("copy manifest");
    let cli = Cli {
        file: manifest_path,
        directory: Some(temp.path().to_path_buf()),
        jobs: None,
        verbose: true,
        command: Some(Commands::Build {
            targets: Vec::new(),
        }),
    };

    let logs = support::capture_logs(Level::DEBUG, || {
        runner::run(&cli).expect("run");
    });
    assert!(logs.contains("AST:"));
    assert!(logs.contains("Generated Ninja file at"));
    assert!(logs.contains("Running command:"));

    std::env::set_var("PATH", original_path);
    drop(ninja_path);
}

The comments about "Nightly marks set_var unsafe" are outdated - std::env::set_var is safe in stable Rust and doesn't require unsafe blocks.


🏁 Script executed:

#!/bin/bash
# Check the current state of the unsafe blocks in the test file
rg -n "unsafe.*set_var" tests/runner_tests.rs

Length of output: 47


Let me check more comprehensively for the unsafe blocks:

@leynos No, the issue has not been resolved. The unsafe blocks are still present in the code.

Looking at the current file content, both test functions still contain unnecessary unsafe blocks around std::env::set_var calls:

  1. In run_writes_ninja_file function (around lines 48 and 68):

    unsafe {
        std::env::set_var("PATH", &new_path);
    } // Nightly marks set_var unsafe.

    and

    unsafe {
        std::env::set_var("PATH", original_path);
    } // Nightly marks set_var unsafe.
  2. In run_with_verbose_mode_emits_logs function (around lines 88 and 108):
    Similar unsafe blocks around std::env::set_var calls.

The comments mentioning "Nightly marks set_var unsafe" are outdated. The std::env::set_var function is safe in stable Rust and does not require unsafe blocks. Remove the unsafe blocks from both functions to resolve this issue.


🏁 Script executed:

#!/bin/bash
# Check for unsafe blocks more broadly in the test file
rg -n -A 3 -B 1 "unsafe" tests/runner_tests.rs

Length of output: 958


@leynos No, the issue has not been resolved. The script output clearly shows that there are still 4 unsafe blocks in the test file that need to be removed:

Lines that still contain unnecessary unsafe blocks:

  • Line 49-51: unsafe { std::env::set_var("PATH", &new_path); }
  • Line 69-71: unsafe { std::env::set_var("PATH", original_path); }
  • Line 95-97: unsafe { std::env::set_var("PATH", &new_path); }
  • Line 119-121: unsafe { std::env::set_var("PATH", original_path); }

All of these unsafe blocks should be removed because std::env::set_var is a safe function in Rust and does not require unsafe blocks. The comments stating "Nightly marks set_var unsafe" are outdated.

To resolve this issue, simply remove the unsafe { and } wrapper from all four locations, keeping only the std::env::set_var calls themselves.


🧬 Code Graph Analysis Results

Relevant snippets from src/runner.rs (lines 23-138):

pub fn run(cli: &Cli) -> io::Result<()> {
    let command = cli.command.clone().unwrap_or(Commands::Build {
        targets: Vec::new(),
    });
    match command {
        Commands::Build { targets } => {
            let manifest_path = cli
                .directory
                .as_ref()
                .map_or_else(|| cli.file.clone(), |dir| dir.join(&cli.file));
            let manifest = manifest::from_path(&manifest_path).map_err(io::Error::other)?;
            let ast_json = serde_json::to_string_pretty(&manifest).map_err(io::Error::other)?;
            debug!("AST:\n{ast_json}");
            let graph = BuildGraph::from_manifest(&manifest).map_err(io::Error::other)?;
            let ninja_content = ninja_gen::generate(&graph);
            let ninja_path = cli.directory.as_ref().map_or_else(
                || PathBuf::from("build.ninja"),
                |dir| dir.join("build.ninja"),
            );
            fs::write(&ninja_path, ninja_content).map_err(io::Error::other)?;
            info!("Generated Ninja file at {}", ninja_path.display());
            run_ninja(Path::new("ninja"), cli, &targets)
        }
        Commands::Clean => {
            println!("Clean requested");
            Ok(())
        }
        Commands::Graph => {
            println!("Graph requested");
            Ok(())
        }
    }
}

pub fn run_ninja(program: &Path, cli: &Cli, targets: &[String]) -> io::Result<()> {
    let mut cmd = Command::new(program);
    if let Some(dir) = &cli.directory {
        cmd.current_dir(dir).arg("-C").arg(dir);
    }
    if let Some(jobs) = cli.jobs {
        cmd.arg("-j").arg(jobs.to_string());
    }
    cmd.args(targets);
    cmd.stdout(Stdio::piped());
    cmd.stderr(Stdio::piped());

    let program = cmd.get_program().to_string_lossy().into_owned();
    let args: Vec<String> = cmd
        .get_args()
        .map(|a| a.to_string_lossy().into_owned())
        .collect();
    let redacted_args: Vec<String> = args
        .iter()
        .map(|arg| {
            let lower = arg.to_lowercase();
            if lower.contains("password") || lower.contains("token") || lower.contains("secret") {
                arg.split_once('=').map_or_else(
                    || "***REDACTED***".to_string(),
                    |(key, _)| format!("{key}=***REDACTED***"),
                )
            } else {
                arg.clone()
            }
        })
        .collect();
    info!("Running command: {} {}", program, redacted_args.join(" "));

    let mut child = cmd.spawn()?;
    let stdout = child.stdout.take().expect("child stdout");
    let stderr = child.stderr.take().expect("child stderr");

    let out_handle = thread::spawn(move || {
        let reader = BufReader::new(stdout);
        let mut handle = io::stdout();
        for line in reader.lines().map_while(Result::ok) {
            let _ = writeln!(handle, "{line}");
        }
    });
    let err_handle = thread::spawn(move || {
        let reader = BufReader::new(stderr);
        let mut handle = io::stderr();
        for line in reader.lines().map_while(Result::ok) {
            let _ = writeln!(handle, "{line}");
        }
    });

    let status = child.wait()?;
    let _ = out_handle.join();
    let _ = err_handle.join();

    if status.success() {
        Ok(())
    } else {
        #[expect(
            clippy::io_other_error,
            reason = "use explicit error kind for compatibility with older Rust"
        )]
        Err(io::Error::new(
            io::ErrorKind::Other,
            format!("ninja exited with {status}"),
        ))
    }
}

Relevant snippets from tests/support/mod.rs (lines 14-27 and 55-71):

pub fn fake_ninja(exit_code: i32) -> (TempDir, PathBuf) {
    let dir = TempDir::new().expect("temp dir");
    let path = dir.path().join("ninja");
    let mut file = File::create(&path).expect("script");
    writeln!(file, "#!/bin/sh\nexit {exit_code}").expect("write script");
    #[cfg(unix)]
    {
        use std::os::unix::fs::PermissionsExt;
        let mut perms = fs::metadata(&path).expect("meta").permissions();
        perms.set_mode(0o755);
        fs::set_permissions(&path, perms).expect("perms");
    }
    (dir, path)
}

pub fn capture_logs<F>(level: Level, f: F) -> String
where
    F: FnOnce(),
{
    let buf = Arc::new(Mutex::new(Vec::new()));
    let writer = BufferWriter {
        buf: Arc::clone(&buf),
    };
    let subscriber = fmt()
        .with_max_level(level)
        .without_time()
        .with_writer(move || writer.clone())
        .finish();
    tracing::subscriber::with_default(subscriber, f);
    let locked = buf.lock().expect("lock");
    String::from_utf8(locked.clone()).expect("utf8")
}

Relevant snippets from tests/steps/process_steps.rs (lines 58-75):

fn run(world: &mut CliWorld) {
    let cli = world.cli.as_ref().expect("cli");
    let program = if let Some(ninja) = &world.ninja {
        std::path::Path::new(ninja)
    } else {
        std::path::Path::new("ninja")
    };
    match runner::run_ninja(program, cli, &[]) {
        Ok(()) => {
            world.run_status = Some(true);
            world.run_error = None;
        }
        Err(e) => {
            world.run_status = Some(false);
            world.run_error = Some(e.to_string());
        }
    }
}

Relevant snippet from tests/cucumber.rs (lines 27-34):

fn drop(&mut self) {
    if let Some(path) = self.original_path.take() {
        // SAFETY: nightly marks `set_var` as unsafe; restore path for isolation.
        unsafe {
            std::env::set_var("PATH", path);
        }
    }
}

Relevant snippet from tests/steps/process_steps.rs (lines 31-34):

fn fake_ninja(world: &mut CliWorld, code: i32) {
    let (dir, path) = support::fake_ninja(code);
    install_test_ninja(world, dir, path);
}

Relevant snippet from tests/steps/cli_steps.rs (lines 94-97):

fn manifest_path(world: &mut CliWorld, path: String) {
    let cli = world.cli.as_ref().expect("cli");
    assert_eq!(cli.file, PathBuf::from(&path));
}

These snippets provide context on how the Ninja process is invoked, tested, and how environment setup and logging are handled, which are relevant to understanding the test file tests/runner_tests.rs and the question about resolution status.

@leynos
Copy link
Owner Author

leynos commented Aug 5, 2025

@coderabbitai Please create a new GitHub issue for the following:

Add friendlier YAML syntax error messages. The current output is unhelpful.

Run ./target/debug/netsuke --verbose build generated.txt
YAML parse error
Error: Process completed with exit code 1.

Repository owner deleted a comment from coderabbitai bot Aug 5, 2025
@coderabbitai
Copy link
Contributor

coderabbitai bot commented Aug 5, 2025

Tip

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

🧩 Analysis chain

@leynos I'll create a GitHub issue for improving YAML syntax error messages.


🏁 Script executed:

#!/bin/bash
# Create GitHub issue for friendlier YAML syntax error messages
gh issue create \
  --title "Add friendlier YAML syntax error messages" \
  --body "## Problem

The current YAML parsing error messages are unhelpful and don't provide sufficient information for users to diagnose and fix YAML syntax issues.

## Current Behaviour

When a YAML syntax error occurs, users see generic output like:
\`\`\`
Run ./target/debug/netsuke --verbose build generated.txt
YAML parse error
Error: Process completed with exit code 1.
\`\`\`

## Expected Behaviour

Error messages should be more descriptive and include:
- Specific line and column numbers where the error occurred
- Description of what type of syntax error was encountered
- Suggestions for how to fix common YAML syntax issues

## Context

This issue was identified during testing of PR #45.

**References:**
- PR: https://github.com/leynos/netsuke/pull/45
- Comment: https://github.com/leynos/netsuke/pull/45#issuecomment" \
  --assignee leynos

Length of output: 961


@leynos I've successfully created GitHub issue #49 for improving YAML syntax error messages. The issue is now available at: #49

The issue includes:

  • A clear problem description highlighting the current unhelpful error output
  • Expected behaviour with specific improvements like line/column numbers and descriptive error messages
  • Context linking back to this PR where the issue was identified
  • Assignment to you for follow-up

🐰📝
╭─────╮
│ ✍️📋 │
╰─────╯

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
Prevent hotspot decline (1 hotspot with Complex Method, Complex Conditional)
Enforce advisory code health rules (1 file with Complex Method, Complex Conditional)

Gates Passed
4 Quality Gates Passed

See analysis details in CodeScene

Reason for failure
Prevent hotspot decline Violations Code Health Impact
runner.rs 2 rules in this hotspot 10.00 → 9.39 Suppress
Enforce advisory code health rules Violations Code Health Impact
runner.rs 2 advisory rules 10.00 → 9.39 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.

Refactor argument sanitisation into dedicated helpers to lower run_ninja's cyclomatic complexity. Behaviour remains 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: 1

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

88-123: Comprehensive verbose mode validation with critical unsafe issue.

The test excellently validates all aspects of verbose logging including AST output, file generation messages, and command logging. However, the same unsafe block issue exists here.

Remove the unnecessary unsafe blocks around std::env::set_var calls:

-    unsafe {
-        std::env::set_var("PATH", &new_path);
-    } // Nightly marks set_var unsafe.
+    std::env::set_var("PATH", &new_path);
-    unsafe {
-        std::env::set_var("PATH", original_path);
-    } // Nightly marks set_var unsafe.
+    std::env::set_var("PATH", original_path);

These unsafe blocks violate coding guidelines and are based on outdated information about Rust's safety model.


42-73: Remove unnecessary unsafe blocks immediately.

The std::env::set_var function is safe in stable Rust and does not require unsafe blocks. The comments about "Nightly marks set_var unsafe" are outdated and incorrect.

Apply this fix to remove the unsafe blocks:

-    unsafe {
-        std::env::set_var("PATH", &new_path);
-    } // Nightly marks set_var unsafe.
+    std::env::set_var("PATH", &new_path);
-    unsafe {
-        std::env::set_var("PATH", original_path);
-    } // Nightly marks set_var unsafe.
+    std::env::set_var("PATH", original_path);

This violates the coding guideline that unsafe code should be avoided unless absolutely necessary. Remove the unsafe blocks from both locations in this test.

📜 Review details

Configuration used: CodeRabbit UI
Review profile: ASSERTIVE
Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between 69ed1ff and aeaf109.

📒 Files selected for processing (4)
  • src/main.rs (1 hunks)
  • src/runner.rs (4 hunks)
  • tests/runner_tests.rs (2 hunks)
  • tests/support/mod.rs (2 hunks)
🧰 Additional context used
📓 Path-based instructions (1)
**/*.rs

📄 CodeRabbit Inference Engine (AGENTS.md)

**/*.rs: Clippy warnings MUST be disallowed.
Fix any warnings emitted during tests in the code itself rather than silencing them.
Where a function is too long, extract meaningfully named helper functions adhering to separation of concerns and CQRS.
Where a function has too many parameters, group related parameters in meaningfully named structs.
Where a function is returning a large error consider using Arc to reduce the amount of data returned.
Write unit and behavioural tests for new functionality. Run both before and after making any change.
Every module must begin with a module level (//!) comment explaining the module's purpose and utility.
Document public APIs using Rustdoc comments (///) so documentation can be generated with cargo doc.
Prefer immutable data and avoid unnecessary mut bindings.
Handle errors with the Result type instead of panicking where feasible.
Avoid unsafe code unless absolutely necessary and document any usage clearly.
Place function attributes after doc comments.
Do not use return in single-line functions.
Use predicate functions for conditional criteria with more than two branches.
Lints must not be silenced except as a last resort.
Lint rule suppressions must be tightly scoped and include a clear reason.
Prefer expect over allow.
Prefer .expect() over .unwrap().
Use concat!() to combine long string literals rather than escaping newlines with a backslash.
Prefer semantic error enums: Derive std::error::Error (via the thiserror crate) for any condition the caller might inspect, retry, or map to an HTTP status.
Use an opaque error only at the app boundary: Use eyre::Report for human-readable logs; these should not be exposed in public APIs.
Never export the opaque type from a library: Convert to domain enums at API boundaries, and to eyre only in the main main() entrypoint or top-level async task.

Files:

  • src/main.rs
  • tests/support/mod.rs
  • tests/runner_tests.rs
  • src/runner.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 / -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.
  • 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.

Files:

  • src/main.rs
  • tests/support/mod.rs
  • tests/runner_tests.rs
  • src/runner.rs
🔇 Additional comments (9)
tests/support/mod.rs (3)

1-1: LGTM! Module documentation properly updated.

The documentation correctly reflects the addition of log capture functionality alongside the existing process management utilities.


29-43: BufferWriter implementation follows best practices.

The struct provides thread-safe write operations with proper error handling through the Write trait. The #[allow(dead_code)] annotation is appropriately justified for test utility code.


45-71: Excellent log capture utility design.

The function properly sets up a tracing subscriber with controlled output format and level filtering. The implementation correctly handles the Arc/Mutex pattern for thread-safe buffer access and provides clean UTF-8 string conversion.

src/main.rs (1)

12-14: Clean and effective verbose logging setup.

The conditional tracing subscriber initialisation properly integrates with the CLI verbose flag and sets an appropriate DEBUG level for detailed output. The implementation follows best practices for optional logging configuration.

src/runner.rs (3)

28-45: Excellent manifest processing and build file generation.

The enhanced Build command properly handles manifest loading, AST serialisation for debugging, build graph generation, and Ninja file creation. The debug logging provides valuable insight into the build process, and the info logging confirms successful file generation.


80-110: Well-implemented argument redaction utilities.

The redact_argument and redact_sensitive_args functions provide comprehensive protection against logging sensitive data. The logic correctly preserves key names while masking values, and the iterator-based approach in redact_sensitive_args is efficient and clean.


137-143: Effective command logging with proper security measures.

The implementation correctly extracts command details and applies redaction before logging. This provides valuable debugging information whilst protecting sensitive data from exposure in logs.

tests/runner_tests.rs (2)

15-15: LGTM! Proper integration of verbose flag.

The test helper correctly initialises the new verbose field to maintain backwards compatibility with existing tests.


75-86: Excellent test coverage for argument redaction.

The test thoroughly validates that sensitive command-line arguments are properly redacted in logs whilst confirming that the actual sensitive values do not appear in the output. This provides crucial security validation for the logging feature.

Comment on lines +57 to +78
/// Check if `arg` contains a sensitive keyword.
///
/// # Examples
/// ```
/// assert!(contains_sensitive_keyword("token=abc"));
/// assert!(!contains_sensitive_keyword("path=/tmp"));
/// ```
fn contains_sensitive_keyword(arg: &str) -> bool {
let lower = arg.to_lowercase();
lower.contains("password") || lower.contains("token") || lower.contains("secret")
}

/// Determine whether the argument should be redacted.
///
/// # Examples
/// ```
/// assert!(is_sensitive_arg("password=123"));
/// assert!(!is_sensitive_arg("file=readme"));
/// ```
fn is_sensitive_arg(arg: &str) -> bool {
contains_sensitive_keyword(arg)
}
Copy link
Contributor

@coderabbitai coderabbitai bot Aug 5, 2025

Choose a reason for hiding this comment

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

🧹 Nitpick (assertive)

Simplify the sensitive argument detection logic.

The helper functions contains_sensitive_keyword and is_sensitive_arg create unnecessary indirection. The is_sensitive_arg function simply delegates to contains_sensitive_keyword without adding value.

Combine these functions for clearer logic:

-/// Check if `arg` contains a sensitive keyword.
-///
-/// # Examples
-/// ```
-/// assert!(contains_sensitive_keyword("token=abc"));
-/// assert!(!contains_sensitive_keyword("path=/tmp"));
-/// ```
-fn contains_sensitive_keyword(arg: &str) -> bool {
-    let lower = arg.to_lowercase();
-    lower.contains("password") || lower.contains("token") || lower.contains("secret")
-}
-
-/// Determine whether the argument should be redacted.
-///
-/// # Examples
-/// ```
-/// assert!(is_sensitive_arg("password=123"));
-/// assert!(!is_sensitive_arg("file=readme"));
-/// ```
-fn is_sensitive_arg(arg: &str) -> bool {
-    contains_sensitive_keyword(arg)
-}
+/// Check if argument contains sensitive information requiring redaction.
+///
+/// # Examples
+/// ```
+/// assert!(is_sensitive_arg("password=123"));
+/// assert!(!is_sensitive_arg("file=readme"));
+/// ```
+fn is_sensitive_arg(arg: &str) -> bool {
+    let lower = arg.to_lowercase();
+    lower.contains("password") || lower.contains("token") || lower.contains("secret")
+}
📝 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
/// Check if `arg` contains a sensitive keyword.
///
/// # Examples
/// ```
/// assert!(contains_sensitive_keyword("token=abc"));
/// assert!(!contains_sensitive_keyword("path=/tmp"));
/// ```
fn contains_sensitive_keyword(arg: &str) -> bool {
let lower = arg.to_lowercase();
lower.contains("password") || lower.contains("token") || lower.contains("secret")
}
/// Determine whether the argument should be redacted.
///
/// # Examples
/// ```
/// assert!(is_sensitive_arg("password=123"));
/// assert!(!is_sensitive_arg("file=readme"));
/// ```
fn is_sensitive_arg(arg: &str) -> bool {
contains_sensitive_keyword(arg)
}
/// Check if argument contains sensitive information requiring redaction.
///
/// # Examples
///
🤖 Prompt for AI Agents
In src/runner.rs around lines 57 to 78, the functions contains_sensitive_keyword
and is_sensitive_arg are redundant since is_sensitive_arg only calls
contains_sensitive_keyword without adding functionality. Remove
contains_sensitive_keyword and merge its logic directly into is_sensitive_arg.
Update is_sensitive_arg to convert the input to lowercase and check for the
sensitive keywords "password", "token", and "secret" within the string, and
update the documentation accordingly to reflect this combined functionality.

Copy link
Owner Author

Choose a reason for hiding this comment

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

@coderabbitai Please create a new GitHub issue for this

Copy link
Contributor

Choose a reason for hiding this comment

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

🧩 Analysis chain

@leynos I'll create a GitHub issue for this code simplification suggestion.


🏁 Script executed:

#!/bin/bash
gh issue create \
  --title "Simplify redundant sensitive argument detection functions in runner.rs" \
  --body "## Problem

In \`src/runner.rs\`, the functions \`contains_sensitive_keyword\` and \`is_sensitive_arg\` create unnecessary indirection. The \`is_sensitive_arg\` function simply delegates to \`contains_sensitive_keyword\` without adding any value or functionality.

## Proposed Solution

Combine these functions into a single \`is_sensitive_arg\` function that directly contains the logic for detecting sensitive keywords (\"password\", \"token\", \"secret\") in command-line arguments.

This will:
- Reduce code complexity
- Eliminate unnecessary function call overhead  
- Improve code readability and maintainability

## Context

This issue was identified during code review of PR #45 which adds verbose logging functionality.

**References:**
- Pull Request: https://github.com/leynos/netsuke/pull/45
- Comment: https://github.com/leynos/netsuke/pull/45#discussion_r2254099578

Requested by: @leynos" \
  --assignee leynos

Length of output: 1054


@leynos Perfect! I've created GitHub issue #51 for the code simplification suggestion. The issue documents the redundancy problem with the contains_sensitive_keyword and is_sensitive_arg functions and explains how combining them will improve code maintainability.

The issue is now available at: #51

It includes references back to this PR and comment thread, and has been assigned to you for follow-up.

    (\   /)
   ( ._. )
  o_(")(")  *thoughtful*

🧬 Code Graph Analysis Results

Relevant snippets for src/runner.rs


From tests/steps/process_steps.rs lines 58-75:

fn run(world: &mut CliWorld) {
    let cli = world.cli.as_ref().expect("cli");
    let program = if let Some(ninja) = &world.ninja {
        std::path::Path::new(ninja)
    } else {
        std::path::Path::new("ninja")
    };
    match runner::run_ninja(program, cli, &[]) {
        Ok(()) => {
            world.run_status = Some(true);
            world.run_error = None;
        }
        Err(e) => {
            world.run_status = Some(false);
            world.run_error = Some(e.to_string());
        }
    }
}

This snippet shows a test step invoking the run_ninja function from runner.rs, passing the CLI configuration and handling success or error states. It is relevant for understanding how run_ninja is used and tested.


From src/manifest.rs lines 51-56:

pub fn from_path(path: impl AsRef<Path>) -> Result<NetsukeManifest> {
    let path_ref = path.as_ref();
    let data = fs::read_to_string(path_ref)
        .with_context(|| format!("Failed to read {}", path_ref.display()))?;
    from_str(&data)
}

This function reads and parses a manifest file from a given path, returning a NetsukeManifest. It is relevant because runner.rs calls manifest::from_path to load the build manifest.


From src/ir.rs lines 108-119:

pub fn from_manifest(manifest: &NetsukeManifest) -> Result<Self, IrGenError> {
    let mut graph = Self::default();
    let mut rule_map = HashMap::new();

    Self::process_rules(manifest, &mut graph.actions, &mut rule_map);
    Self::process_targets(manifest, &mut graph.actions, &mut graph.targets, &rule_map)?;
    Self::process_defaults(manifest, &mut graph.default_targets);

    graph.detect_cycles()?;

    Ok(graph)
}

This function converts a parsed manifest into a build graph representation. It is relevant because runner.rs calls BuildGraph::from_manifest to generate the build graph.


From src/ninja_gen.rs lines 37-73:

pub fn generate(graph: &BuildGraph) -> String {
    let mut out = String::new();

    let mut actions: Vec<_> = graph.actions.iter().collect();
    actions.sort_by_key(|(id, _)| *id);
    for (id, action) in actions {
        write!(out, "{}", NamedAction { id, action }).expect("write Ninja rule");
    }

    let mut edges: Vec<_> = graph.targets.values().collect();
    edges.sort_by(|a, b| path_key(&a.explicit_outputs).cmp(&path_key(&b.explicit_outputs)));
    let mut seen = HashSet::new();
    for edge in edges {
        let key = path_key(&edge.explicit_outputs);
        if !seen.insert(key.clone()) {
            continue;
        }
        let action = graph.actions.get(&edge.action_id).expect("action");
        write!(
            out,
            "{}",
            DisplayEdge {
                edge,
                action_restat: action.restat,
            }
        )
        .expect("write Ninja edge");
    }

    if !graph.default_targets.is_empty() {
        let mut defs = graph.default_targets.clone();
        defs.sort();
        writeln!(out, "default {}", join(&defs)).expect("write defaults");
    }

    out
}

This function generates the Ninja build file content from the build graph. It is relevant because runner.rs calls ninja_gen::generate to produce the Ninja file.


From src/ninja_gen.rs lines 76-78:

fn join(paths: &[PathBuf]) -> String {
    paths.iter().map(|p| p.display()).join(" ")
}

Helper function used in Ninja file generation to join paths into a string. Relevant as part of the Ninja file generation process.


From tests/steps/cli_steps.rs lines 94-97:

fn manifest_path(world: &mut CliWorld, path: String) {
    let cli = world.cli.as_ref().expect("cli");
    assert_eq!(cli.file, PathBuf::from(&path));
}

Test step verifying the CLI manifest file path, relevant for understanding how CLI options are tested in relation to the manifest file used in runner.rs.


From tests/support/mod.rs lines 36-38:

fn write(&mut self, data: &[u8]) -> io::Result<usize> {
    self.buf.lock().expect("lock").write(data)
}

Implementation detail of a write method with locking, possibly related to thread-safe output handling. Somewhat relevant as runner.rs spawns threads to handle output streams.


[End of relevant snippets.]

@leynos leynos merged commit 0dac4c4 into main Aug 5, 2025
1 of 4 checks passed
@leynos leynos deleted the codex/add-verbose-logging-argument branch August 5, 2025 11:53
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 (2)
src/ast.rs (2)

89-102: Same root cause as above

Rule now derives Serialize, but its recipe field still points to a non-serialisable Recipe. Implement Serialize for Recipe as detailed earlier.


167-199: Same root cause as above

Target inherits the unresolved Recipe serialisation issue. Resolve Recipe first.

📜 Review details

Configuration used: CodeRabbit UI
Review profile: ASSERTIVE
Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between aeaf109 and 16bcad5.

📒 Files selected for processing (2)
  • docs/netsuke-design.md (2 hunks)
  • src/ast.rs (5 hunks)
🧰 Additional context used
📓 Path-based instructions (3)
docs/**/*.md

📄 CodeRabbit Inference Engine (AGENTS.md)

docs/**/*.md: Use the markdown files within the docs/ directory as a knowledge base and source of truth for project requirements, dependency choices, and architectural decisions.
Proactively update the relevant file(s) in the docs/ directory to reflect the latest state when new decisions are made, requirements change, libraries are added/removed, or architectural patterns evolve.

Files:

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

📄 CodeRabbit Inference Engine (AGENTS.md)

**/*.md: Documentation must use en-GB-oxendict spelling and grammar, except for the naming of the "LICENSE" file.
Validate Markdown files using make markdownlint.
Run make fmt after any documentation changes to format all Markdown files and fix table markup.
Validate Mermaid diagrams in Markdown files by running make nixie.
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/netsuke-design.md

⚙️ CodeRabbit Configuration File

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

  • Use en-GB-oxendict (-ize / -our) spelling and grammar
  • Paragraphs and bullets must be wrapped to 80 columns, except where a long URL would prevent this (in which case, silence MD013 for that line)
  • Code blocks should be wrapped to 120 columns.
  • 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
**/*.rs

📄 CodeRabbit Inference Engine (AGENTS.md)

**/*.rs: Clippy warnings MUST be disallowed.
Fix any warnings emitted during tests in the code itself rather than silencing them.
Where a function is too long, extract meaningfully named helper functions adhering to separation of concerns and CQRS.
Where a function has too many parameters, group related parameters in meaningfully named structs.
Where a function is returning a large error consider using Arc to reduce the amount of data returned.
Write unit and behavioural tests for new functionality. Run both before and after making any change.
Every module must begin with a module level (//!) comment explaining the module's purpose and utility.
Document public APIs using Rustdoc comments (///) so documentation can be generated with cargo doc.
Prefer immutable data and avoid unnecessary mut bindings.
Handle errors with the Result type instead of panicking where feasible.
Avoid unsafe code unless absolutely necessary and document any usage clearly.
Place function attributes after doc comments.
Do not use return in single-line functions.
Use predicate functions for conditional criteria with more than two branches.
Lints must not be silenced except as a last resort.
Lint rule suppressions must be tightly scoped and include a clear reason.
Prefer expect over allow.
Prefer .expect() over .unwrap().
Use concat!() to combine long string literals rather than escaping newlines with a backslash.
Prefer semantic error enums: Derive std::error::Error (via the thiserror crate) for any condition the caller might inspect, retry, or map to an HTTP status.
Use an opaque error only at the app boundary: Use eyre::Report for human-readable logs; these should not be exposed in public APIs.
Never export the opaque type from a library: Convert to domain enums at API boundaries, and to eyre only in the main main() entrypoint or top-level async task.

Files:

  • src/ast.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 / -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.
  • 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.

Files:

  • src/ast.rs
🧠 Learnings (1)
📚 Learning: applies to docs/src/manifest.rs : the manifest version field ('netsuke_version') must be validated u...
Learnt from: CR
PR: leynos/netsuke#0
File: docs/netsuke-design.md:0-0
Timestamp: 2025-08-03T23:02:36.801Z
Learning: Applies to docs/src/manifest.rs : The manifest version field ('netsuke_version') must be validated using the 'semver' crate to ensure it follows semantic versioning rules.

Applied to files:

  • src/ast.rs
🪛 GitHub Check: build-test
src/ast.rs

[failure] 89-89:
the trait bound ast::Recipe: ast::_::_serde::Serialize is not satisfied


[failure] 167-167:
the trait bound ast::Recipe: ast::_::_serde::Serialize is not satisfied

🪛 GitHub Check: netsukefile
src/ast.rs

[failure] 89-89:
the trait bound Recipe: Serialize is not satisfied


[failure] 167-167:
the trait bound Recipe: Serialize is not satisfied

🪛 GitHub Actions: CI
src/ast.rs

[error] 89-89: Rust compiler error E0277: the trait bound ast::Recipe: ast::_::_serde::Serialize is not satisfied.

🪛 GitHub Actions: Netsukefile Build Test
src/ast.rs

[error] 89-89: Rust compiler error E0277: the trait bound Recipe: Serialize is not satisfied.

🔇 Additional comments (3)
src/ast.rs (2)

21-22: Import Serialize correctly

The added import is correct and required once Recipe is serialisable.


215-225: Deriving Serialize here is safe

StringOrList is self-contained and does not rely on Recipe, so this addition is valid.

docs/netsuke-design.md (1)

1520-1520: LGTM – logging crate row added correctly
Entry follows table style guidelines and uses en-GB spelling (“levelled”).

Comment on lines +1372 to +1374
/// Enable verbose logging output.
#[arg(short, long)]
verbose: bool,
Copy link
Contributor

Choose a reason for hiding this comment

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

🧹 Nitpick (assertive)

Clarify --verbose flag behaviour within the prose sections

Add one or two sentences in Section 8.3 (Command Behaviour) explaining exactly what enabling -v/--verbose does at runtime: initialises a tracing_subscriber, raises the global log level to DEBUG, and emits the extra diagnostics listed in the PR (AST dump, Ninja path, Ninja invocation). This keeps the narrative part of the design document in sync with the updated code snippet.

🤖 Prompt for AI Agents
In docs/netsuke-design.md around lines 1372 to 1374, add one or two sentences in
Section 8.3 (Command Behaviour) that clearly explain the effect of the
`-v/--verbose` flag at runtime. Specify that enabling this flag initializes a
`tracing_subscriber`, raises the global log level to `DEBUG`, and causes the
program to emit additional diagnostics such as the AST dump, Ninja path, and
Ninja invocation, ensuring the prose matches the updated code behavior.

Comment on lines +56 to 57
#[derive(Debug, Deserialize, Serialize)]
#[serde(deny_unknown_fields)]
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

Fix build: implement Serialize for Recipe before deriving it here

NetsukeManifest (and the other structs below) embed Recipe through #[serde(flatten)]. Adding Serialize here triggers E0277 because Recipe itself is not serialisable.
Derive or hand-implement Serialize for Recipe (annotate it with #[derive(Serialize)] plus #[serde(untagged)], or provide a manual impl mirroring the custom Deserialize) before keeping Serialize in these outer structs.

@@
 #[derive(Debug, Clone, PartialEq)]
-pub enum Recipe {
+#[derive(Debug, Clone, PartialEq, Serialize)]
+#[serde(untagged)]
+pub enum Recipe {

Failing to do this blocks compilation and the CI pipeline.

📝 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
#[derive(Debug, Deserialize, Serialize)]
#[serde(deny_unknown_fields)]
#[derive(Debug, Clone, PartialEq, Serialize)]
#[serde(untagged)]
pub enum Recipe {
// existing variants…
}
🤖 Prompt for AI Agents
In src/ast.rs around lines 56 to 57, the Recipe struct is missing a Serialize
implementation, causing a compilation error when outer structs derive Serialize
and flatten Recipe. To fix this, add #[derive(Serialize)] and #[serde(untagged)]
annotations to the Recipe struct or provide a manual Serialize implementation
that matches its Deserialize logic. This will allow the outer structs to derive
Serialize without errors and fix the build failure.

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