Skip to content

Implement initial CLI#20

Merged
leynos merged 7 commits intomainfrom
codex/implement-build-command-cli-structure
Jul 16, 2025
Merged

Implement initial CLI#20
leynos merged 7 commits intomainfrom
codex/implement-build-command-cli-structure

Conversation

@leynos
Copy link
Copy Markdown
Owner

@leynos leynos commented Jul 15, 2025

Summary

  • add clap-based CLI implementation
  • add CLI unit tests and cucumber behavioural tests
  • document CLI design decision and update roadmap

Testing

  • make fmt
  • make lint
  • make test
  • make markdownlint

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

Summary by Sourcery

Implement the initial Clap-based command-line interface with build, clean, and graph subcommands, add comprehensive unit and BDD tests, and update documentation and project configuration accordingly.

New Features:

  • Add a Clap-based CLI with global options (file, directory, jobs) and subcommands build, clean, and graph, defaulting to build when no subcommand is provided.

Build:

  • Add clap as a dependency and introduce rstest, cucumber, and tokio as dev-dependencies with a Cucumber test harness in Cargo.toml.

Documentation:

  • Document CLI design decisions in the design spec and mark the CLI implementation as complete in the roadmap.

Tests:

  • Add rstest-powered unit tests for CLI parsing and validation, and Cucumber feature files with step definitions for behaviour-driven CLI tests.

@sourcery-ai
Copy link
Copy Markdown
Contributor

sourcery-ai Bot commented Jul 15, 2025

Reviewer's Guide

Introduces a clap-based CLI with file, directory, and jobs options; build, clean, and graph subcommands defaulting to build; comprehensive unit and behavioural (Cucumber) tests; documentation updates; and required dependency additions and test harness configuration.

Class diagram for the new CLI structure

classDiagram
    class Cli {
        +PathBuf file
        +Option<PathBuf> directory
        +Option<usize> jobs
        +Option<Commands> command
    }
    class Commands {
    }
    Cli --> Commands
    class Build {
        +Vec<String> targets
    }
    class Clean {
    }
    class Graph {
    }
    Commands <|-- Build
    Commands <|-- Clean
    Commands <|-- Graph
Loading

File-Level Changes

Change Details Files
Implement clap-based CLI parsing and dispatch
  • Defined a Cli struct with Parser derive and options for file, directory, and jobs
  • Introduced Commands enum with Build, Clean, and Graph variants
  • Updated main.rs to parse arguments, default to Build, and match on subcommands
src/cli.rs
src/main.rs
src/lib.rs
Add comprehensive unit and behavioural tests for CLI
  • Added rstest unit tests covering valid parsing and error scenarios
  • Created Cucumber feature files and step definitions for CLI behaviour
  • Configured Cucumber runner entrypoint in tests/cucumber.rs
tests/cli_tests.rs
tests/features/cli.feature
tests/cucumber.rs
tests/steps/cli_steps.rs
tests/steps/mod.rs
Update project documentation and roadmap
  • Documented CLI design decisions and default behaviour in design doc
  • Marked initial CLI implementation as complete in the roadmap
docs/netsuke-design.md
docs/roadmap.md
Add dependencies and configure test harness
  • Added clap, rstest, cucumber, and tokio dependencies
  • Configured a custom Cucumber test harness in Cargo.toml
Cargo.toml

Tips and commands

Interacting with Sourcery

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

Customizing Your Experience

Access your dashboard to:

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

Getting Help

@coderabbitai
Copy link
Copy Markdown
Contributor

coderabbitai Bot commented Jul 15, 2025

Warning

Rate limit exceeded

@leynos has exceeded the limit for the number of commits or files that can be reviewed per hour. Please wait 7 minutes and 50 seconds before requesting another review.

⌛ How to resolve this issue?

After the wait time has elapsed, a review can be triggered using the @coderabbitai review command as a PR comment. Alternatively, push new commits to this PR.

We recommend that you space out your commits to avoid hitting the rate limit.

🚦 How do rate limits work?

CodeRabbit enforces hourly rate limits for each developer per organization.

Our paid plans have higher rate limits than the trial, open-source and free plans. In all cases, we re-allow further reviews after a brief timeout.

Please see our FAQ for further information.

📥 Commits

Reviewing files that changed from the base of the PR and between e6c33ea and f533a0d.

📒 Files selected for processing (1)
  • tests/steps/cli_steps.rs (1 hunks)

Summary by CodeRabbit

New Features

  • Introduced a user-friendly command line interface (CLI) with support for build, clean, and graph commands, including options for manifest file, working directory, and parallel jobs.
  • Default command behaviour added: running without a subcommand triggers a build.
  • Enhanced error handling and validation for CLI arguments.

Documentation

  • Updated and expanded design documentation and roadmap to reflect the new CLI structure and decisions.

Tests

  • Added comprehensive unit and behaviour-driven tests for CLI parsing, validation, and error scenarios using both parameterised and Cucumber-style tests.

Summary by CodeRabbit

  • New Features

    • Introduced a command-line interface (CLI) with support for build, clean, and graph commands, including options for manifest file, working directory, and parallel jobs.
    • Default behaviour runs the build command when no subcommand is specified.
  • Documentation

    • Added design decisions for the CLI and updated the roadmap to reflect completed CLI structure.
  • Tests

    • Added comprehensive unit and behaviour-driven tests for CLI parsing, including feature scenarios and step definitions.

Walkthrough

Introduce a new command-line interface (CLI) for the Netsuke application using the clap crate, along with supporting documentation, library structure, and comprehensive automated tests. Update the project manifest to include dependencies and test configurations. Implement CLI parsing, command dispatch, and behaviour-driven tests using both rstest and cucumber.

Changes

Files/Paths Change Summary
Cargo.toml Add clap dependency; add rstest, cucumber, tokio dev-dependencies; add cucumber test target configuration.
docs/netsuke-design.md Add CLI design decisions: use of clap derive API, optional Build subcommand, -C flag aligned with Ninja, error validation.
docs/roadmap.md Mark initial CLI structure as completed in roadmap checklist.
src/cli.rs Add CLI struct and Commands enum with Build, Clean, Graph subcommands using clap derive API.
src/lib.rs Add public cli and runner modules exposing CLI and runner logic.
src/main.rs Implement CLI parsing and command dispatch logic with default to Build command.
src/runner.rs Add command dispatch logic handling Build, Clean, and Graph commands.
tests/cli_tests.rs Add parameterised unit tests for CLI parsing success and error cases using rstest.
tests/cucumber.rs Define Cucumber test world struct and async runner for feature tests.
tests/features/cli.feature Add CLI parsing feature scenarios for default command, file override, unknown command, and missing option value.
tests/steps/cli_steps.rs Implement Cucumber step definitions for CLI parsing and validation steps.
tests/steps/mod.rs Add cli_steps submodule declaration.

Sequence Diagram(s)

sequenceDiagram
    participant User
    participant CLI (clap)
    participant Main
    participant CommandHandler

    User->>CLI (clap): Provide command-line arguments
    CLI (clap)->>Main: Parse into Cli struct
    Main->>CommandHandler: Match on Cli.command
    alt Build command or no command
        CommandHandler->>Main: Print build targets
    else Clean command
        CommandHandler->>Main: Print clean request
    else Graph command
        CommandHandler->>Main: Print graph request
    end
Loading

Poem

In the land of Netsuke, the CLI awakes,
With clap at the helm, no more parsing headaches!
Tests march in rows, both feature and step,
Docs now explain how the arguments are kept.
The roadmap ticks forward, the journey anew—
Command-line adventures, now waiting for you!
🐇✨

✨ 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/implement-build-command-cli-structure

🪧 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.
    • @coderabbitai modularize this function.
  • 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.
    • @coderabbitai help me debug CodeRabbit configuration file.

Support

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

Note: Be mindful of the bot's finite context window. It's strongly recommended to break down tasks such as reading entire modules into smaller chunks. For a focused discussion, use review comments to chat about specific files and their changes, instead of using the PR comments.

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 auto-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
Copy Markdown
Contributor

@sourcery-ai sourcery-ai Bot left a comment

Choose a reason for hiding this comment

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

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

Prompt for AI Agents
Please address the comments from this code review:
## Individual Comments

### Comment 1
<location> `tests/cli_tests.rs:6` </location>
<code_context>
+use rstest::rstest;
+use std::path::PathBuf;
+
+#[rstest]
+#[case(vec!["netsuke"], PathBuf::from("Netsukefile"), None, None, Commands::Build { targets: Vec::new() })]
+#[case(
+    vec!["netsuke", "--file", "alt.yml", "-C", "work", "-j", "4", "build", "a", "b"],
+    PathBuf::from("alt.yml"),
+    Some(PathBuf::from("work")),
+    Some(4),
+    Commands::Build { targets: vec!["a".into(), "b".into()] },
+)]
+fn parse_cli(
+    #[case] argv: Vec<&str>,
+    #[case] file: PathBuf,
+    #[case] directory: Option<PathBuf>,
+    #[case] jobs: Option<usize>,
+    #[case] expected_cmd: Commands,
+) {
+    let cli = Cli::try_parse_from(argv).expect("parse");
+    assert_eq!(cli.file, file);
+    assert_eq!(cli.directory, directory);
+    assert_eq!(cli.jobs, jobs);
+    let command = cli.command.unwrap_or(Commands::Build {
+        targets: Vec::new(),
+    });
+    assert_eq!(command, expected_cmd);
+}
</code_context>

<issue_to_address>
Missing tests for error conditions and invalid CLI arguments.

Please add tests for invalid or malformed CLI arguments (e.g., unknown subcommands, missing required values, invalid types for --jobs) to verify error handling and messaging.

Suggested implementation:

```rust
use clap::error::ErrorKind;


```

```rust
#[rstest]
#[case(vec!["netsuke", "unknowncmd"], ErrorKind::UnknownArgument)]
#[case(vec!["netsuke", "--file"], ErrorKind::EmptyValue)]
#[case(vec!["netsuke", "-j", "notanumber"], ErrorKind::ValueValidation)]
#[case(vec!["netsuke", "--file", "alt.yml", "-C"], ErrorKind::EmptyValue)]
fn parse_cli_errors(
    #[case] argv: Vec<&str>,
    #[case] expected_error: ErrorKind,
) {
    let err = Cli::try_parse_from(argv).unwrap_err();
    assert_eq!(err.kind(), expected_error);
}

```
</issue_to_address>

### Comment 2
<location> `tests/steps/cli_steps.rs:45` </location>
<code_context>
+    clippy::needless_pass_by_value,
+    reason = "Cucumber requires owned String arguments"
+)]
+#[then(expr = "the manifest path is {string}")]
+fn manifest_path(world: &mut CliWorld, path: String) {
+    let cli = world.cli.as_ref().expect("cli");
+    assert_eq!(cli.file, PathBuf::from(path));
+}
+
</code_context>

<issue_to_address>
No Cucumber steps for error scenarios or invalid input.

Add Cucumber steps to test error handling for invalid or incomplete CLI arguments.

Suggested implementation:

```rust
#[allow(
    clippy::needless_pass_by_value,
    reason = "Cucumber requires owned String arguments"
)]
#[when(expr = "the CLI is parsed with {string}")]
fn parse_cli(world: &mut CliWorld, args: String) {
    let tokens: Vec<String> = if args.is_empty() {
        vec!["netsuke".to_string()]
    } else {
        std::iter::once("netsuke".to_string())

```

```rust
    } else {
        std::iter::once("netsuke".to_string())
    }
    .chain(args.split_whitespace().map(str::to_string))
    .collect();

    // Try to parse CLI, store result or error in world
    match Cli::try_parse_from(&tokens) {
        Ok(cli) => {
            world.cli = Some(cli);
            world.cli_error = None;
        }
        Err(e) => {
            world.cli = None;
            world.cli_error = Some(e.to_string());
        }
    }
}

/// Step: When the CLI is parsed with invalid arguments {string}
#[when(expr = "the CLI is parsed with invalid arguments {string}")]
fn parse_cli_invalid(world: &mut CliWorld, args: String) {
    let tokens: Vec<String> = std::iter::once("netsuke".to_string())
        .chain(args.split_whitespace().map(str::to_string))
        .collect();

    match Cli::try_parse_from(&tokens) {
        Ok(cli) => {
            world.cli = Some(cli);
            world.cli_error = None;
        }
        Err(e) => {
            world.cli = None;
            world.cli_error = Some(e.to_string());
        }
    }
}

/// Step: Then an error should be returned
#[then("an error should be returned")]
fn error_should_be_returned(world: &mut CliWorld) {
    assert!(world.cli_error.is_some(), "Expected an error, but none was returned");
}

/// Step: Then the error message should contain {string}
#[then(expr = "the error message should contain {string}")]
fn error_message_should_contain(world: &mut CliWorld, expected: String) {
    let error = world.cli_error.as_ref().expect("No error was returned");
    assert!(
        error.contains(&expected),
        "Error message '{}' does not contain expected '{}'",
        error,
        expected
    );
}

```

- Ensure that the `CliWorld` struct has a `cli_error: Option<String>` field. If not, add it.
- Add corresponding Cucumber scenarios in your `.feature` files to use these new steps.
</issue_to_address>

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

Comment thread tests/cli_tests.rs
Comment thread tests/steps/cli_steps.rs Outdated
Copy link
Copy Markdown
Contributor

@coderabbitai coderabbitai Bot left a comment

Choose a reason for hiding this comment

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

Actionable comments posted: 11

📜 Review details

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

📥 Commits

Reviewing files that changed from the base of the PR and between ccba565 and 81493b4.

⛔ Files ignored due to path filters (1)
  • Cargo.lock is excluded by !**/*.lock
📒 Files selected for processing (11)
  • Cargo.toml (2 hunks)
  • docs/netsuke-design.md (1 hunks)
  • docs/roadmap.md (1 hunks)
  • src/cli.rs (1 hunks)
  • src/lib.rs (1 hunks)
  • src/main.rs (1 hunks)
  • tests/cli_tests.rs (1 hunks)
  • tests/cucumber.rs (1 hunks)
  • tests/features/cli.feature (1 hunks)
  • tests/steps/cli_steps.rs (1 hunks)
  • tests/steps/mod.rs (1 hunks)
🧰 Additional context used
📓 Path-based instructions (5)
docs/**/*.md

Instructions used from:

Sources:
📄 CodeRabbit Inference Engine

  • AGENTS.md
**/*.md

Instructions used from:

Sources:
📄 CodeRabbit Inference Engine

  • AGENTS.md

⚙️ CodeRabbit Configuration File

Cargo.toml

Instructions used from:

Sources:
📄 CodeRabbit Inference Engine

  • AGENTS.md
**/*.rs

Instructions used from:

Sources:
📄 CodeRabbit Inference Engine

  • AGENTS.md

⚙️ CodeRabbit Configuration File

tests/**/*.rs

Instructions used from:

Sources:
📄 CodeRabbit Inference Engine

  • AGENTS.md
🧬 Code Graph Analysis (2)
tests/cli_tests.rs (1)
tests/steps/cli_steps.rs (1)
  • parse_cli (12-21)
tests/steps/cli_steps.rs (1)
tests/cli_tests.rs (1)
  • parse_cli (15-30)
🪛 LanguageTool
docs/netsuke-design.md

[grammar] ~1267-~1267: An apostrophe ‘s’ denotes possession. Did you mean to use the plural form of the noun (no apostrophe)?
Context: ...Decisions The CLI is implemented using clap's derive API in src/cli.rs. The Build...

(NOUN_APOSTROPHE_S_VERB)


[uncategorized] ~1268-~1268: Possible missing comma found.
Context: ...src/cli.rs. The Build subcommand is optional so that invoking netsuke without a su...

(AI_HYDRA_LEO_MISSING_COMMA)

🔇 Additional comments (11)
Cargo.toml (1)

54-56: Cucumber test configuration is correctly implemented.

The test configuration properly disables the default harness for cucumber tests.

docs/roadmap.md (1)

15-15: Roadmap update correctly reflects CLI implementation completion.

The task completion marking accurately reflects the implementation of the initial CLI structure.

tests/steps/mod.rs (1)

1-1: Module declaration follows correct patterns.

The cli_steps module declaration is properly structured for organising cucumber step definitions.

src/lib.rs (1)

1-6: Library structure follows coding guidelines correctly.

The module-level documentation properly explains the library's purpose and current scope. The public cli module declaration is appropriate for the current implementation phase.

tests/features/cli.feature (1)

1-12: Feature test structure follows Gherkin conventions correctly.

The scenarios provide good coverage of CLI parsing functionality with clear, testable steps. The feature description and scenario names are meaningful and focused.

src/main.rs (1)

1-18: CLI entry point implementation looks correct.

The main function correctly implements the CLI parsing logic as documented in the design. The default command handling using unwrap_or(Commands::Build { targets: vec![] }) properly addresses the requirement that invoking netsuke without a subcommand defaults to building default targets.

The placeholder print statements are appropriate for this initial CLI implementation phase.

tests/cucumber.rs (1)

1-14: Cucumber test setup is well-structured.

The CliWorld struct provides a clean way to maintain CLI state across cucumber test steps. The async main function and feature file discovery pattern follow cucumber best practices.

The integration with the step definitions module creates a proper separation of concerns for behavior-driven testing.

src/cli.rs (3)

1-5: Module documentation follows coding guidelines.

The module-level documentation properly explains the module's purpose and references the design documentation, adhering to the requirement that every module begin with a //! comment.


9-27: CLI structure correctly implements the documented design.

The Cli struct accurately reflects the design specification from docs/netsuke-design.md. The use of -C for the directory flag properly mirrors Ninja's convention as documented. The default value for the manifest file matches the expected "Netsukefile" filename.


29-43: Commands enum provides the required subcommands.

The Commands enum correctly implements the three documented subcommands: Build, Clean, and Graph. The Build command properly accepts a vector of target strings, enabling the documented netsuke build [targets...] usage pattern.

The derive attributes for Debug, PartialEq, Eq, and Clone support testing and comparison operations appropriately.

tests/cli_tests.rs (1)

6-30: CLI test coverage is comprehensive and well-structured.

The parameterised test cases using rstest provide excellent coverage of CLI parsing scenarios. The test validates both minimal arguments and complex combinations, ensuring the default command behaviour works correctly.

The test properly validates the default command handling that matches the main function's logic using unwrap_or(Commands::Build { targets: Vec::new() }).

Comment thread Cargo.toml
Comment thread Cargo.toml Outdated
Comment thread docs/netsuke-design.md
Comment thread tests/cli_tests.rs Outdated
Comment thread tests/steps/cli_steps.rs Outdated
Comment thread tests/steps/cli_steps.rs
Comment thread tests/steps/cli_steps.rs
Comment thread tests/steps/cli_steps.rs Outdated
Comment thread tests/steps/cli_steps.rs Outdated
Comment thread tests/steps/cli_steps.rs Outdated
@leynos
Copy link
Copy Markdown
Owner Author

leynos commented Jul 16, 2025

@sourcery-ai resolve

@leynos
Copy link
Copy Markdown
Owner Author

leynos commented Jul 16, 2025

@sourcery-ai review

@leynos
Copy link
Copy Markdown
Owner Author

leynos commented Jul 16, 2025

@coderabbitai review

@coderabbitai
Copy link
Copy Markdown
Contributor

coderabbitai Bot commented Jul 16, 2025

✅ Actions performed

Review triggered.

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

Copy link
Copy Markdown
Contributor

@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 - here's some feedback:

  • Consider moving the CLI execution logic out of main.rs into a separate runner module so that parsing stays decoupled from business logic.
  • Instead of manually unwrapping the optional subcommand in main, you can use clap’s default_subcommand attribute to handle the default build command at parse time.
  • The Cucumber feature tests cover basic parsing; adding scenarios for the directory (-C) and jobs (-j) flags would help validate those options end-to-end.
Prompt for AI Agents
Please address the comments from this code review:
## Overall Comments
- Consider moving the CLI execution logic out of main.rs into a separate runner module so that parsing stays decoupled from business logic.
- Instead of manually unwrapping the optional subcommand in main, you can use clap’s default_subcommand attribute to handle the default `build` command at parse time.
- The Cucumber feature tests cover basic parsing; adding scenarios for the directory (`-C`) and jobs (`-j`) flags would help validate those options end-to-end.

## Individual Comments

### Comment 1
<location> `tests/features/cli.feature:2` </location>
<code_context>
+# This file is automatically @generated by Cargo.
+# It is not intended for manual editing.
+version = 4
+
+[[package]]
+name = "addr2line"
</code_context>

<issue_to_address>
Please ensure the feature file covers all subcommands and flag combinations.

Include scenarios for each subcommand and flag combination, as well as error cases like unknown subcommands or missing arguments, to ensure comprehensive CLI coverage.
</issue_to_address>

### Comment 2
<location> `tests/cucumber.rs:1` </location>
<code_context>
+use cucumber::World;
+
+#[derive(Debug, Default, World)]
</code_context>

<issue_to_address>
Consider replacing the Cucumber-based test setup with simple integration tests using assert_cmd for improved clarity and maintainability.

Consider dropping the full Cucumber setup in favor of a single integration test with assert_cmd. You can remove the `cucumber` crate, the `World` derive, your custom `main`, and the `tests/features` folder, then add something like this in `tests/cli.rs`:

```rust
// tests/cli.rs
use assert_cmd::prelude::*;
use predicates::prelude::*;
use std::process::Command;

#[test]
fn shows_help() {
    let mut cmd = Command::cargo_bin("netsuke-cli").unwrap();
    cmd.arg("--help");
    cmd.assert()
        .success()
        .stdout(predicate::str::contains("Usage"));
}

#[test]
fn error_on_invalid_subcommand() {
    let mut cmd = Command::cargo_bin("netsuke-cli").unwrap();
    cmd.arg("no-such-cmd");
    cmd.assert()
        .failure()
        .stderr(predicate::str::contains("unknown subcommand"));
}
```

Steps to apply:
1. Remove `cucumber = …` from Cargo.toml.
2. Delete `#[derive(World)] CliWorld`, `mod steps`, and your custom `main` in tests.
3. Create `tests/cli.rs` with `assert_cmd`-based tests as shown above.
</issue_to_address>

### Comment 3
<location> `tests/features/cli.feature:1` </location>
<code_context>
+Feature: CLI parsing
+
+  Scenario: Build is the default command
</code_context>

<issue_to_address>
Consider replacing the external Gherkin feature file with a table-driven test in Go to keep tests inline and reduce context-switching.

Consider replacing the external Gherkin feature with a small, table‐driven test in your codebase. This keeps everything in one place and avoids context‐switching:

```go
// cli_test.go

func TestCLIParsing(t *testing.T) {
    tests := []struct {
        name            string
        args            []string
        wantCmd         string
        wantManifest    string
        wantTarget      string
        wantErr         bool
        wantErrContains string
    }{
        {
            name:     "default to build",
            args:     []string{},
            wantCmd:  "build",
        },
        {
            name:         "override manifest and target",
            args:         []string{"--file", "alt.yml", "build", "target"},
            wantCmd:      "build",
            wantManifest: "alt.yml",
            wantTarget:   "target",
        },
        {
            name:            "unknown command",
            args:            []string{"unknown"},
            wantErr:         true,
            wantErrContains: "unknown",
        },
        {
            name:            "missing file value",
            args:            []string{"--file"},
            wantErr:         true,
            wantErrContains: "--file",
        },
    }

    for _, tc := range tests {
        t.Run(tc.name, func(t *testing.T) {
            cmd, manifest, target, err := ParseCLI(tc.args)
            if tc.wantErr {
                if err == nil || !strings.Contains(err.Error(), tc.wantErrContains) {
                    t.Fatalf("expected error containing %q, got %v", tc.wantErrContains, err)
                }
                return
            }
            if err != nil {
                t.Fatal("unexpected error:", err)
            }
            if cmd != tc.wantCmd {
                t.Errorf("cmd = %q; want %q", cmd, tc.wantCmd)
            }
            if manifest != tc.wantManifest {
                t.Errorf("manifest = %q; want %q", manifest, tc.wantManifest)
            }
            if target != tc.wantTarget {
                t.Errorf("target = %q; want %q", target, tc.wantTarget)
            }
        })
    }
}
```

This keeps all parsing tests inline, reduces cognitive overhead, and covers the same scenarios without external files.
</issue_to_address>

### Comment 4
<location> `tests/steps/cli_steps.rs:17` </location>
<code_context>
+    reason = "Cucumber requires owned String arguments"
+)]
+#[when(expr = "the CLI is parsed with {string}")]
+fn parse_cli(world: &mut CliWorld, args: String) {
+    let tokens: Vec<String> = if args.is_empty() {
+        vec!["netsuke".to_string()]
</code_context>

<issue_to_address>
Consider extracting repeated CLI parsing and build command extraction logic into helper functions to reduce code duplication.

```rust
// introduce once at the top
fn apply_cli(world: &mut CliWorld, args: &str) {
    let tokens = std::iter::once("netsuke".to_string())
        .chain(args.split_whitespace().map(str::to_string))
        .collect::<Vec<_>>();
    match Cli::try_parse_from(tokens) {
        Ok(cli) => {
            world.cli = Some(cli);
            world.cli_error = None;
        }
        Err(e) => {
            world.cli = None;
            world.cli_error = Some(e.to_string());
        }
    }
}

#[when(expr = "the CLI is parsed with {string}")]
fn parse_cli(world: &mut CliWorld, args: String) {
    apply_cli(world, &args);
}

#[when(expr = "the CLI is parsed with invalid arguments {string}")]
fn parse_cli_invalid(world: &mut CliWorld, args: String) {
    apply_cli(world, &args);
}

// helper for extracting the Build command
fn extract_build(world: &CliWorld) -> &Commands::Build {
    let cmd = world
        .cli
        .as_ref()
        .expect("cli")
        .command
        .as_ref()
        .unwrap_or(&Commands::Build { targets: vec![] });
    match cmd {
        b @ Commands::Build { .. } => b,
        other => panic!("Expected build command, got {:?}", other),
    }
}

#[then("the command is build")]
fn command_is_build(world: &mut CliWorld) {
    let _ = extract_build(world);
}

#[then(expr = "the first target is {string}")]
fn first_target(world: &mut CliWorld, target: String) {
    assert_eq!(extract_build(world).targets.first(), Some(&target));
}
```

This pulls the common CLI-parsing and build-extraction logic into small helpers, removing duplicated code while keeping all existing step definitions intact.
</issue_to_address>

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

Comment thread tests/features/cli.feature
Comment thread tests/cucumber.rs
Comment thread tests/features/cli.feature
Comment thread tests/steps/cli_steps.rs Outdated
Copy link
Copy Markdown
Contributor

@coderabbitai coderabbitai Bot left a comment

Choose a reason for hiding this comment

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

Actionable comments posted: 8

🔭 Outside diff range comments (1)
tests/features/cli.feature (1)

1-33: Add scenarios for missing CLI features and edge cases.

The feature file lacks coverage for several CLI capabilities defined in src/cli.rs:

  • Clean and Graph subcommands are not tested
  • Multiple build targets scenario is missing (e.g., "build target1 target2")
  • Short vs long flag variations are not validated
  • Edge cases like empty string targets or invalid job counts

Add these scenarios to ensure comprehensive CLI validation:

Scenario: Clean command removes artifacts
  When the CLI is parsed with "clean"
  Then parsing succeeds
  And the command is clean

Scenario: Graph command outputs dependency graph
  When the CLI is parsed with "graph"
  Then parsing succeeds
  And the command is graph

Scenario: Build with multiple targets
  When the CLI is parsed with "build target1 target2"
  Then parsing succeeds
  And the command is build
  And the target list contains "target1" and "target2"

Scenario: Short flags work correctly
  When the CLI is parsed with "-f alt.yml -C work -j 2"
  Then parsing succeeds
  And the manifest path is "alt.yml"
  And the working directory is "work"
  And the job count is 2
♻️ Duplicate comments (3)
tests/steps/cli_steps.rs (3)

69-72: Use preferred lint suppression attribute.

Replace #[allow] with #[expect] to follow the coding guidelines:

-#[allow(
+#[expect(
     clippy::needless_pass_by_value,
     reason = "Cucumber requires owned String arguments"
 )]

79-82: Use preferred lint suppression attribute.

Replace #[allow] with #[expect] to follow the coding guidelines:

-#[allow(
+#[expect(
     clippy::needless_pass_by_value,
     reason = "Cucumber requires owned String arguments"
 )]

88-91: Use preferred lint suppression attribute.

Replace #[allow] with #[expect] to follow the coding guidelines:

-#[allow(
+#[expect(
     clippy::needless_pass_by_value,
     reason = "Cucumber requires owned String arguments"
 )]
📜 Review details

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

📥 Commits

Reviewing files that changed from the base of the PR and between 6df4841 and a22e20b.

📒 Files selected for processing (9)
  • docs/netsuke-design.md (1 hunks)
  • docs/roadmap.md (1 hunks)
  • src/cli.rs (1 hunks)
  • src/lib.rs (1 hunks)
  • src/main.rs (1 hunks)
  • src/runner.rs (1 hunks)
  • tests/cli_tests.rs (1 hunks)
  • tests/features/cli.feature (1 hunks)
  • tests/steps/cli_steps.rs (1 hunks)
🧰 Additional context used
📓 Path-based instructions (4)
**/*.rs

Instructions used from:

Sources:
📄 CodeRabbit Inference Engine

  • AGENTS.md

⚙️ CodeRabbit Configuration File

docs/**/*.md

Instructions used from:

Sources:
📄 CodeRabbit Inference Engine

  • AGENTS.md
**/*.md

Instructions used from:

Sources:
📄 CodeRabbit Inference Engine

  • AGENTS.md

⚙️ CodeRabbit Configuration File

tests/**/*.rs

Instructions used from:

Sources:
📄 CodeRabbit Inference Engine

  • AGENTS.md
🧬 Code Graph Analysis (2)
src/main.rs (2)
src/cli.rs (1)
  • parse_with_default (32-40)
src/runner.rs (1)
  • run (9-23)
tests/steps/cli_steps.rs (1)
tests/cli_tests.rs (1)
  • parse_cli (16-38)
🪛 LanguageTool
docs/netsuke-design.md

[grammar] ~1267-~1267: An apostrophe ‘s’ denotes possession. Did you mean to use the plural form of the noun (no apostrophe)?
Context: ...Decisions The CLI is implemented using clap's derive API in src/cli.rs. Clap's `def...

(NOUN_APOSTROPHE_S_VERB)


[uncategorized] ~1268-~1268: Possible missing comma found.
Context: ... attribute marks Build as the default subcommand so invoking netsuke with no explicit ...

(AI_HYDRA_LEO_MISSING_COMMA)

🔇 Additional comments (7)
docs/roadmap.md (1)

15-17: Documentation update accurately reflects implementation completion.

The roadmap correctly marks the initial CLI implementation as complete, which aligns with the actual code changes in this PR.

src/lib.rs (1)

1-7: Clean library structure with proper module documentation.

The library root correctly exposes the CLI and runner modules with clear documentation that follows the required //! format and explains the module's purpose.

src/main.rs (1)

1-6: Exemplary main function implementation.

The main function correctly demonstrates separation of concerns by handling only CLI parsing and delegation to the runner module. The use of parse_with_default() ensures proper default command behavior.

src/runner.rs (1)

1-23: Well-structured command dispatcher with proper documentation.

The runner module correctly implements command dispatch logic with appropriate default handling and follows the required documentation format. The placeholder implementation is suitable for this initial CLI phase.

docs/netsuke-design.md (1)

1265-1274: Comprehensive CLI design documentation with accurate technical details.

The new design decisions section effectively documents the CLI implementation approach, including the use of clap's derive API, default command behavior, and testing strategy. The technical details align well with the actual implementation.

Note: The possessive usage "clap's derive API" is correct as previously clarified, despite static analysis flags.

src/cli.rs (1)

1-5: Improve module documentation with examples.

The module documentation lacks clear examples as required by the coding guidelines. Add practical usage examples to demonstrate CLI parsing:

 //! Command line interface definition using clap.
 //!
 //! This module defines the [`Cli`] structure and its subcommands.
 //! It mirrors the design described in `docs/netsuke-design.md`.
+//!
+//! # Examples
+//!
+//! ```
+//! use netsuke::cli::Cli;
+//! 
+//! // Parse CLI with default build command
+//! let cli = Cli::parse_with_default();
+//! 
+//! // Parse specific arguments
+//! let cli = Cli::try_parse_from(vec!["netsuke", "--file", "build.yml", "build", "target"]);
+//! ```

Likely an incorrect or invalid review comment.

tests/steps/cli_steps.rs (1)

12-31: Confirm default logic consistency

Both apply_cli and Cli::parse_with_default check command.is_none() and set Commands::Build { targets: Vec::new() } when none is provided. No changes required.

Comment thread docs/netsuke-design.md Outdated
Comment thread src/cli.rs Outdated
Comment thread src/cli.rs Outdated
Comment thread src/cli.rs
Comment thread tests/cli_tests.rs
Comment thread tests/cli_tests.rs Outdated
Comment thread tests/cli_tests.rs Outdated
Comment thread tests/steps/cli_steps.rs Outdated
@leynos
Copy link
Copy Markdown
Owner Author

leynos commented Jul 16, 2025

@coderabbitai pause

@coderabbitai
Copy link
Copy Markdown
Contributor

coderabbitai Bot commented Jul 16, 2025

✅ Actions performed

Reviews paused.

@leynos
Copy link
Copy Markdown
Owner Author

leynos commented Jul 16, 2025

@coderabbitai resume

@coderabbitai
Copy link
Copy Markdown
Contributor

coderabbitai Bot commented Jul 16, 2025

✅ Actions performed

Reviews resumed.

Copy link
Copy Markdown
Contributor

@coderabbitai coderabbitai Bot left a comment

Choose a reason for hiding this comment

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

Actionable comments posted: 1

♻️ Duplicate comments (3)
tests/steps/cli_steps.rs (3)

82-85: Use preferred lint suppression attribute.

The coding guidelines forbid #[allow] and require #[expect] with narrow scope and clear reasons.

Apply this fix:

-#[allow(
+#[expect(
     clippy::needless_pass_by_value,
     reason = "Cucumber requires owned String arguments"
 )]

92-95: Use preferred lint suppression attribute.

The coding guidelines forbid #[allow] and require #[expect] with narrow scope and clear reasons.

Apply this fix:

-#[allow(
+#[expect(
     clippy::needless_pass_by_value,
     reason = "Cucumber requires owned String arguments"
 )]

102-105: Use preferred lint suppression attribute.

The coding guidelines forbid #[allow] and require #[expect] with narrow scope and clear reasons.

Apply this fix:

-#[allow(
+#[expect(
     clippy::needless_pass_by_value,
     reason = "Cucumber requires owned String arguments"
 )]
📜 Review details

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

📥 Commits

Reviewing files that changed from the base of the PR and between a22e20b and e6c33ea.

📒 Files selected for processing (6)
  • docs/netsuke-design.md (1 hunks)
  • src/cli.rs (1 hunks)
  • src/runner.rs (1 hunks)
  • tests/cli_tests.rs (1 hunks)
  • tests/features/cli.feature (1 hunks)
  • tests/steps/cli_steps.rs (1 hunks)
🧰 Additional context used
📓 Path-based instructions (2)
**/*.rs

Instructions used from:

Sources:
⚙️ CodeRabbit Configuration File

**/*.md

Instructions used from:

Sources:
⚙️ CodeRabbit Configuration File

🧬 Code Graph Analysis (1)
tests/cli_tests.rs (2)
tests/steps/cli_steps.rs (1)
  • parse_cli (41-43)
src/cli.rs (1)
  • parse_from_with_default (56-64)
🪛 LanguageTool
docs/netsuke-design.md

[grammar] ~1267-~1267: An apostrophe ‘s’ denotes possession. Did you mean to use the plural form of the noun (no apostrophe)?
Context: ...Decisions The CLI is implemented using clap's derive API in src/cli.rs. Clap's `def...

(NOUN_APOSTROPHE_S_VERB)

🔇 Additional comments (13)
src/runner.rs (1)

8-23: LGTM! Clean command dispatch implementation.

The function correctly implements the command dispatch pattern with appropriate default handling. The current placeholder implementation with print statements is suitable for initial development and testing.

docs/netsuke-design.md (1)

1265-1275: LGTM! Comprehensive CLI design documentation.

The new subsection effectively documents the CLI implementation approach, architectural decisions, and testing strategy. The content accurately reflects the implementation and provides valuable context for future development.

tests/features/cli.feature (1)

1-60: LGTM! Comprehensive CLI test coverage.

The feature file provides excellent coverage of CLI functionality with well-structured scenarios for both success and error cases. The tests appropriately validate command parsing, flag handling, and error message content.

tests/cli_tests.rs (2)

11-32: LGTM! Well-structured parameterised tests.

The test function effectively covers CLI parsing success scenarios with good use of rstest parameterisation. The test cases appropriately validate different flag combinations and default behavior.


34-42: LGTM! Comprehensive error scenario testing.

The error test function appropriately validates various failure conditions with correct error kind assertions. The test cases cover the main error scenarios effectively.

src/cli.rs (4)

9-21: LGTM! Robust jobs parameter validation.

The validation function correctly enforces reasonable limits for the jobs parameter with clear error messages. The use of a constant for the maximum value is good practice.


23-41: LGTM! Well-structured CLI definition.

The Cli struct is properly defined with appropriate clap attributes, sensible defaults, and clear documentation. The field types and validation are appropriate for the use case.


43-76: LGTM! Clean default command handling.

The parsing methods are well-implemented with a functional approach. The documentation is clear, including appropriate panic conditions, and the must_use attributes are good practice.


78-92: LGTM! Clean command enum definition.

The Commands enum is well-structured with appropriate derive attributes and clear documentation. The use of unit structs for parameterless commands is idiomatic Rust.

tests/steps/cli_steps.rs (4)

1-10: Module documentation and imports look excellent.

The module documentation follows the required format and clearly explains the purpose. The imports are well-organised and appropriate for the functionality.


36-52: CLI parsing step definitions are well-implemented.

The functions properly use #[expect] for lint suppression with clear reasons and delegate to the apply_cli helper function, reducing code duplication.


54-80: Command validation steps are well-structured.

The functions use appropriate helper functions, idiomatic Rust patterns like matches!, and follow the coding guidelines by using .expect() over .unwrap().


118-137: Error handling steps are well-implemented.

The functions use descriptive assertion messages and follow the coding guidelines with proper lint suppression using #[expect].

Comment thread tests/steps/cli_steps.rs
@leynos leynos merged commit fe28d0c into main Jul 16, 2025
1 check passed
@leynos leynos deleted the codex/implement-build-command-cli-structure branch July 16, 2025 22:33
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