Skip to content

Skeleton + init|run cmd#2

Merged
Bechma merged 12 commits intocyberfabric:mainfrom
Bechma:setup
Feb 18, 2026
Merged

Skeleton + init|run cmd#2
Bechma merged 12 commits intocyberfabric:mainfrom
Bechma:setup

Conversation

@Bechma
Copy link
Copy Markdown
Collaborator

@Bechma Bechma commented Feb 18, 2026

Summary by CodeRabbit

  • New Features

    • Full CLI with subcommands for setup, init, build, lint, test, and run (argument parsing wired).
    • Project init from templates (local or git) to a destination path.
    • Run command supports one-shot and watch modes with auto-regenerate/restart and optional telemetry.
    • Config-driven module parsing to discover modules and assemble dependencies for generated projects.
  • Chores

    • Repository converted to a multi-crate workspace.
    • Git ignore updated to exclude IDE metadata.

Signed-off-by: Bechma <19294519+Bechma@users.noreply.github.com>
Signed-off-by: Bechma <19294519+Bechma@users.noreply.github.com>
Signed-off-by: Bechma <19294519+Bechma@users.noreply.github.com>
Signed-off-by: Bechma <19294519+Bechma@users.noreply.github.com>
Signed-off-by: Bechma <19294519+Bechma@users.noreply.github.com>
Signed-off-by: Bechma <19294519+Bechma@users.noreply.github.com>
Signed-off-by: Bechma <19294519+Bechma@users.noreply.github.com>
@coderabbitai
Copy link
Copy Markdown
Contributor

coderabbitai bot commented Feb 18, 2026

Note

Reviews paused

It looks like this branch is under active development. To avoid overwhelming you with review comments due to an influx of new commits, CodeRabbit has automatically paused this review. You can configure this behavior by changing the reviews.auto_review.auto_pause_after_reviewed_commits setting.

Use the following commands to manage reviews:

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

Use the checkboxes below for quick actions:

  • ▶️ Resume reviews
  • 🔍 Trigger review
📝 Walkthrough

Walkthrough

Adds a Rust workspace with two member crates (cli, module-parser). Implements a Clap-based CLI (setup, lint, test, run, build). The run flow loads config, extracts dependencies, generates a templated server Cargo layout, supports watch mode with FS watcher and cargo-run orchestration. module-parser provides config models and crate/AST parsing.

Changes

Cohort / File(s) Summary
Repo config
\.gitignore, Cargo.toml
Uncomments .idea/ in .gitignore. Adds workspace root Cargo.toml with members crates/cli, crates/module-parser, resolver = 3, workspace metadata, shared dependencies and workspace lints (forbid unsafe_code).
CLI crate manifest & entry
crates/cli/Cargo.toml, crates/cli/src/main.rs
Adds crates/cli manifest and binary entry wiring subcommands Setup, Lint, Test, Run, Build, dispatching to each .run() handler.
CLI argument modules (scaffolding)
crates/cli/src/common.rs, crates/cli/src/build/mod.rs, crates/cli/src/lint/mod.rs, crates/cli/src/test/mod.rs
Adds CommonArgs and per-command Clap arg structs (BuildArgs, LintArgs, TestArgs); most .run() methods are placeholders/unimplemented.
CLI run implementation
crates/cli/src/run/...
crates/cli/src/run/mod.rs, crates/cli/src/run/run_loop.rs, crates/cli/src/run/templates.rs
Implements run flow: canonicalizes paths, constructs RunLoop, loads config, extracts dependencies, generates server Cargo layout and main.rs via Liquid templates, supports one-shot and watch modes, manages cargo run loop, FS watcher, OTEL flag and graceful coordination.
CLI setup subcommands
crates/cli/src/setup/...
crates/cli/src/setup/mod.rs, crates/cli/src/setup/init.rs, crates/cli/src/setup/config.rs, crates/cli/src/setup/module.rs, crates/cli/src/setup/tools.rs
Adds setup command group with init (cargo-generate scaffolding implemented) and scaffolded tools, module, config subcommands (mostly placeholder .run() implementations).
Module-parser crate manifest
crates/module-parser/Cargo.toml
Adds crates/module-parser manifest using workspace metadata and shared dependencies.
Module-parser library & parsing
crates/module-parser/src/lib.rs, crates/module-parser/src/config.rs, crates/module-parser/src/metadata.rs, crates/module-parser/src/module_rs.rs
Introduces Config model and serialization helpers (serialize None version as "*"), create_dependencies, get_module_name_from_crate (cargo-metadata), and retrieve_module_rs (syn-based parsing of module.rs for modkit attributes), plus public re-exports.

Sequence Diagram(s)

sequenceDiagram
    actor User
    participant CLI
    participant RunArgs
    participant RunLoop
    participant ConfigLoader as Config
    participant Watcher
    participant Templates
    participant Cargo

    User->>CLI: run --path . [--watch]
    CLI->>RunArgs: parse args
    RunArgs->>RunLoop: new(canonical_path, config_path)
    activate RunLoop
    RunLoop->>ConfigLoader: load config & deps
    ConfigLoader-->>RunLoop: Config + deps
    alt watch mode
        RunLoop->>Watcher: watch config & dep paths
        Watcher-->>RunLoop: change event
        loop on change
            RunLoop->>Templates: render Cargo.toml & main.rs
            Templates-->>RunLoop: rendered files
            RunLoop->>Cargo: spawn/manage cargo run
            Cargo-->>User: build/run output
        end
    else one-shot
        RunLoop->>Templates: render files
        RunLoop->>Cargo: cargo run once
        Cargo-->>User: build/run output
    end
    deactivate RunLoop
Loading

Estimated code review effort

🎯 4 (Complex) | ⏱️ ~45 minutes

Poem

🐰 I hopped through crates with nimble feet,

templates stitched and watcher beats.
Workspaces paired, modules found their name,
cargo hums, the run loop plays its game.
A rabbit cheers: build, watch, and tame!

🚥 Pre-merge checks | ✅ 1 | ❌ 2

❌ Failed checks (1 warning, 1 inconclusive)

Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 3.03% which is insufficient. The required threshold is 80.00%. Write docstrings for the functions missing them to satisfy the coverage threshold.
Title check ❓ Inconclusive The title partially describes the changeset but is abbreviated and unclear—'skeleton' and 'init|run cmd' do not clearly convey the main changes to a teammate scanning history. Clarify the title to describe the main changes more explicitly, such as 'Add CLI skeleton with init and run commands' or 'Initialize CLI framework with setup and run subcommands'.
✅ Passed checks (1 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.

✏️ Tip: You can configure your own custom pre-merge checks in the settings.

✨ Finishing Touches
  • 📝 Generate docstrings
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Post copyable unit tests in a comment

Warning

Review ran into problems

🔥 Problems

Git: Failed to clone repository. Please run the @coderabbitai full review command to re-trigger a full review. If the issue persists, set path_filters to include or exclude specific files.


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

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

🧹 Nitpick comments (8)
crates/cli/src/lint/mod.rs (1)

3-11: Consider including CommonArgs for consistency.

Unlike BuildArgs and ConfigArgs, LintArgs doesn't flatten CommonArgs. If the lint command might need access to the configuration file (e.g., for lint rules or settings), consider adding:

+use crate::common::CommonArgs;
+
 #[derive(Args)]
 pub struct LintArgs {
     #[arg(long)]
     clippy: bool,
     #[arg(long)]
     dylint: bool,
     #[arg(long)]
     pattern: Option<String>,
+    #[command(flatten)]
+    common_args: CommonArgs,
 }

If lint is intentionally standalone without configuration, this can be ignored.

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@crates/cli/src/lint/mod.rs` around lines 3 - 11, LintArgs currently omits
CommonArgs unlike BuildArgs/ConfigArgs; to add consistent CLI config handling,
add a field like `common: CommonArgs` to the `LintArgs` struct and annotate it
to flatten the sub-options (e.g., using the clap flatten attribute such as
`#[clap(flatten)]` or the project's existing flatten attribute pattern), then
update any places that construct or pattern-match on `LintArgs` to account for
the new `common` field so the lint command can access configuration options.
crates/module-parser/src/metadata.rs (2)

6-6: Prefer &Path over &PathBuf in function signatures.

Using &PathBuf is redundant since &Path is more general and accepts both &Path and &PathBuf via deref coercion.

♻️ Suggested change
-pub fn get_module_name_from_crate(path: &PathBuf) -> anyhow::Result<HashMap<String, ConfigModule>> {
+pub fn get_module_name_from_crate(path: &Path) -> anyhow::Result<HashMap<String, ConfigModule>> {
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@crates/module-parser/src/metadata.rs` at line 6, Change the function
signature of get_module_name_from_crate to accept a &Path instead of &PathBuf:
update the signature in metadata.rs and any direct callers to pass their
Path/PathBuf (relying on deref coercion), and adjust any documentation/comments
referring to the parameter type; ensure imports remain the same and run cargo
build/tests to catch any remaining mismatched references to
get_module_name_from_crate.

20-22: Silently swallowing errors may hide configuration issues.

Printing to stderr and continuing means callers have no indication that some modules failed to parse. Consider returning partial results with a list of errors, or at least logging with a structured logger.

♻️ Alternative: collect warnings
+    let mut warnings = Vec::new();
     for pkg in res.packages {
         for t in &pkg.targets {
             if t.is_lib() {
                 match super::module_rs::retrieve_module_rs(&pkg, t.clone()) {
                     Ok(module) => {
                         members.insert(module.0, module.1);
                     }
                     Err(e) => {
-                        eprintln!("{e}");
+                        warnings.push(format!("{}: {e}", pkg.name));
                     }
                 };
             }
         }
     }
+    for w in &warnings {
+        tracing::warn!("{w}");
+    }
     Ok(members)
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@crates/module-parser/src/metadata.rs` around lines 20 - 22, The Err arm that
currently does eprintln!("{e}") should not silently swallow parse failures;
replace the eprintln call in that Err(e) branch with logic that (1) records the
error into a collection (e.g., push e or (module_id, e) into a Vec<ParseError>)
and (2) emits a structured log (e.g., tracing::error!(error = %e, "module parse
failed")). Then update the surrounding function's signature (the function that
contains this match/Err branch) to return either a Result with partial successes
and an errors Vec or a tuple/struct like (Vec<ModuleMetadata>, Vec<ParseError>)
so callers can see which modules failed; ensure any callers are updated to
handle the new return type and propagate or surface the collected errors instead
of ignoring them.
crates/cli/src/run/mod.rs (1)

29-38: canonicalize() fails if path doesn't exist.

Both config and path are canonicalized, which requires the paths to exist. If a user runs the command before creating the config file or target directory, the error message "can't canonicalize path" is unhelpful.

Consider checking existence first and providing a clearer error, or deferring canonicalization until after validation.

♻️ Suggested improvement
         let config_path = self
             .common_args
             .config
             .canonicalize()
-            .context("can't canonicalize path")?;
+            .with_context(|| format!("config file not found: {}", self.common_args.config.display()))?;

         let path = self
             .path
             .canonicalize()
-            .context("can't canonicalize path")?;
+            .with_context(|| format!("module path not found: {}", self.path.display()))?;
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@crates/cli/src/run/mod.rs` around lines 29 - 38, The current use of
canonicalize() on self.common_args.config and self.path (producing config_path
and path) will error if those paths don't exist; change the logic in run::mod.rs
to first check existence (e.g., using exists or metadata) for
self.common_args.config and self.path and return a clearer error message if
missing, or defer calling canonicalize() until after validation/creation of the
file/directory; update the error contexts from generic "can't canonicalize path"
to specific messages referencing config_path / self.common_args.config and path
/ self.path so users see which resource is missing.
crates/cli/src/setup/module.rs (1)

9-13: Minor inconsistency: todo!() vs unimplemented!().

Other placeholder implementations in this PR use unimplemented!("Not implemented yet") (e.g., tools.rs, config.rs, build/mod.rs), while this uses todo!("gimme time"). Consider aligning for consistency.

♻️ Suggested change
     pub fn run(&self) -> anyhow::Result<()> {
-        todo!("gimme time")
+        unimplemented!("Not implemented yet")
     }
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@crates/cli/src/setup/module.rs` around lines 9 - 13, The ModuleArgs::run
implementation uses todo!("gimme time") which is inconsistent with other
placeholders; replace the todo! call in the ModuleArgs::run function with the
project-standard unimplemented!("Not implemented yet") so the placeholder
matches other modules (see ModuleArgs::run, tools.rs, config.rs, build::mod).
crates/module-parser/src/lib.rs (1)

5-6: Consider explicit re-exports instead of glob exports.

Glob re-exports (pub use config::*) can make it harder to track the public API surface and may accidentally expose internal items if the submodules grow. Explicit exports provide better control.

This is a minor suggestion for a library crate where API clarity matters.

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@crates/module-parser/src/lib.rs` around lines 5 - 6, Replace the glob
re-exports `pub use config::*` and `pub use metadata::*` with explicit named
re-exports to make the crate's public API clear: inspect the `config` and
`metadata` modules and export only the intended public types/functions (e.g.,
`pub use config::{TypeA, parse_config, ConfigError}` and `pub use
metadata::{Metadata, MetadataBuilder}`) by listing their identifiers in `lib.rs`
instead of `*`; ensure any items you want hidden remain non-`pub` in the
submodules and update the named lists when you intentionally add new public
items.
crates/cli/src/setup/init.rs (1)

31-42: Edge case: path ending with / or root paths may fail unexpectedly.

When self.path is / or ends with a trailing separator, file_name() returns None, causing a vague "path is strange" error. Consider canonicalizing the path first or providing a clearer error message.

♻️ Suggested improvement
+        let path = self.path.canonicalize().unwrap_or_else(|_| self.path.clone());
         let name = self
-            .path
+            path
             .file_name()
-            .context("path is strange")?
+            .context("cannot derive project name: path has no final component")?
             .to_str()
-            .context("name is strange")?;
+            .context("cannot derive project name: path contains invalid UTF-8")?;
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@crates/cli/src/setup/init.rs` around lines 31 - 42, The code assumes
self.path.file_name() is always Some, which fails for root or trailing-separator
paths; after creating the directory, canonicalize the path and use the
canonicalized Path (e.g., let canon = self.path.canonicalize().context("failed
to canonicalize path")?) before calling canon.file_name(), and replace the vague
"path is strange" message with a clearer one like "cannot determine directory
name from path (root or trailing separator?)"; update uses of
file_name()/to_str() to operate on the canonicalized Path and propagate a more
descriptive error.
crates/module-parser/src/module_rs.rs (1)

62-102: Validate non‑string module args to avoid silent misconfigurations.
Right now non-string name/deps entries are silently ignored, which can mask config errors.

♻️ Suggested validation
         if meta.path.is_ident("name") {
             let value = meta.value()?;
             let lit: Lit = value.parse()?;
-            if let Lit::Str(lit_str) = lit {
-                name = Some(lit_str.value());
-            }
+            let Lit::Str(lit_str) = lit else {
+                return Err(meta.error("name must be a string literal"));
+            };
+            name = Some(lit_str.value());
         } else if meta.path.is_ident("deps") {
@@
                 let lit: Lit = content.parse()?;
-                if let Lit::Str(lit_str) = lit {
-                    deps.push(lit_str.value());
-                }
+                let Lit::Str(lit_str) = lit else {
+                    return Err(meta.error("deps entries must be string literals"));
+                };
+                deps.push(lit_str.value());
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@crates/module-parser/src/module_rs.rs` around lines 62 - 102, The
parse_module_args function currently ignores non-string values for "name" and
"deps" (and silently accepts bad tokens in "capabilities"); update
parse_module_args to validate and error on invalid types instead of skipping:
when parsing meta.path "name" or each entry in "deps" parse the value to a Lit
and if it is not Lit::Str return an anyhow::Error (use anyhow::bail or context)
that includes which key ("name" or "deps") and the offending token; likewise,
for "capabilities" ensure each entry parses to a syn::Ident and bail with a
clear message if not; return these errors so ModuleInfo is only constructed when
values are valid.
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Inline comments:
In `@Cargo.toml`:
- Line 23: Replace the wildcard dependency for liquid with a pinned semver
version: locate the liquid entry in Cargo.toml (the line currently reading
liquid = { version = "*" }) and change it to a specific stable release such as
"0.26.11" or a bounded semver like "^0.26.11" (or ">=0.26.11, <0.27.0") to
ensure reproducible builds and avoid accidental breaking upgrades.

In `@crates/cli/src/run/mod.rs`:
- Around line 17-19: The doc comment above the command-line flag `otel` is
incorrect (it reads "Watch for changes"); update the doc comment for the `otel`
field (in the struct containing `#[arg(long)] otel: bool`) to a correct
description such as enabling OpenTelemetry tracing (or similar accurate text),
and ensure the `watch` flag's comment remains "Watch for changes" if present so
the two flags are not confused.

In `@crates/cli/src/run/run_loop.rs`:
- Around line 33-115: The watcher loop currently uses while let Ok(Ok(event)) =
fs_rx.recv(), which exits on Ok(Err(_)) and shuts down watch mode on any watcher
error; change the loop in run (in run_loop.rs) to explicitly match fs_rx.recv()
in a loop and handle three cases: Ok(Ok(event)) -> existing event processing
(use is_config_change, reload config, reconcile watched_paths, send
RunSignal::Rerun, etc.), Ok(Err(err)) -> log the watcher error (eprintln! or
processLogger) and continue without shutting down the runner, Err(_) -> break
the loop (channel closed) and proceed to send RunSignal::Stop and join
runner_handle; reference fs_rx, signal_tx, RunSignal, watched_paths,
current_deps and the config reloading/generation logic when moving code into the
Ok(Ok(event)) branch.

In `@crates/cli/src/run/templates.rs`:
- Around line 61-64: The generated use statements break on crate names with
hyphens; update the mapping over dependencies.keys() (the closure that builds
the use lines) to sanitize crate names by replacing '-' with '_' before
formatting (e.g., compute a sanitized_name from name via replace('-', '_') and
then format!("use {sanitized_name} as _;\n")). This ensures valid Rust
identifiers are emitted for crates like "my-module".
- Around line 66-69: The template currently uses
config_path.display().to_string() which on Windows yields backslashes that
become problematic in generated Rust string literals; update the liquid::object!
entry for "config_path" to produce a safe string by normalizing or escaping
Windows separators — for example replace config_path.display().to_string() with
something like config_path.to_string_lossy().replace('\\','/') (or alternatively
.to_string_lossy().replace('\\','\\\\') to escape backslashes) so the generated
code compiles on Windows.

In `@crates/cli/src/setup/mod.rs`:
- Around line 20-35: The match in SetupCommand::run currently delegates to
tools::ToolsArgs::run and config::ConfigArgs::run which call unimplemented!()
and will panic; update SetupCommand::run to detect the Tools and Config variants
and return a user-facing error (e.g., Err(anyhow::anyhow!("'setup tools' is not
implemented yet") or anyhow::bail!("...")) instead of invoking args.run()),
while leaving Init and Module to call their run() methods; reference
SetupCommand::run, tools::ToolsArgs::run, and config::ConfigArgs::run when
making the change.

In `@crates/module-parser/src/config.rs`:
- Around line 10-21: In Config::create_dependencies detect when two modules map
to the same dependency key (the package string after package.replace("-", "_"))
before inserting into the dependencies HashMap: compute the key (e.g., let key =
package.replace("-", "_")), check dependencies.contains_key(&key) and if true
bail! with a clear error that names the conflicting module and key (or otherwise
return an Err) to avoid silent overwrites; otherwise insert the module.metadata
as currently done.

In `@crates/module-parser/src/module_rs.rs`:
- Around line 8-38: In retrieve_module_rs, metadata.path is derived from
src.parent(), which breaks for crates whose lib is not under src; change it to
use the package manifest location instead: set ConfigModuleMetadata.path to
package.manifest_path.parent().map(|p| p.display().to_string()) (preserving the
Option), so the module path reflects the crate root from the Cargo.toml
location; update the assignment inside the ConfigModule creation in
retrieve_module_rs accordingly.

---

Nitpick comments:
In `@crates/cli/src/lint/mod.rs`:
- Around line 3-11: LintArgs currently omits CommonArgs unlike
BuildArgs/ConfigArgs; to add consistent CLI config handling, add a field like
`common: CommonArgs` to the `LintArgs` struct and annotate it to flatten the
sub-options (e.g., using the clap flatten attribute such as `#[clap(flatten)]`
or the project's existing flatten attribute pattern), then update any places
that construct or pattern-match on `LintArgs` to account for the new `common`
field so the lint command can access configuration options.

In `@crates/cli/src/run/mod.rs`:
- Around line 29-38: The current use of canonicalize() on
self.common_args.config and self.path (producing config_path and path) will
error if those paths don't exist; change the logic in run::mod.rs to first check
existence (e.g., using exists or metadata) for self.common_args.config and
self.path and return a clearer error message if missing, or defer calling
canonicalize() until after validation/creation of the file/directory; update the
error contexts from generic "can't canonicalize path" to specific messages
referencing config_path / self.common_args.config and path / self.path so users
see which resource is missing.

In `@crates/cli/src/setup/init.rs`:
- Around line 31-42: The code assumes self.path.file_name() is always Some,
which fails for root or trailing-separator paths; after creating the directory,
canonicalize the path and use the canonicalized Path (e.g., let canon =
self.path.canonicalize().context("failed to canonicalize path")?) before calling
canon.file_name(), and replace the vague "path is strange" message with a
clearer one like "cannot determine directory name from path (root or trailing
separator?)"; update uses of file_name()/to_str() to operate on the
canonicalized Path and propagate a more descriptive error.

In `@crates/cli/src/setup/module.rs`:
- Around line 9-13: The ModuleArgs::run implementation uses todo!("gimme time")
which is inconsistent with other placeholders; replace the todo! call in the
ModuleArgs::run function with the project-standard unimplemented!("Not
implemented yet") so the placeholder matches other modules (see ModuleArgs::run,
tools.rs, config.rs, build::mod).

In `@crates/module-parser/src/lib.rs`:
- Around line 5-6: Replace the glob re-exports `pub use config::*` and `pub use
metadata::*` with explicit named re-exports to make the crate's public API
clear: inspect the `config` and `metadata` modules and export only the intended
public types/functions (e.g., `pub use config::{TypeA, parse_config,
ConfigError}` and `pub use metadata::{Metadata, MetadataBuilder}`) by listing
their identifiers in `lib.rs` instead of `*`; ensure any items you want hidden
remain non-`pub` in the submodules and update the named lists when you
intentionally add new public items.

In `@crates/module-parser/src/metadata.rs`:
- Line 6: Change the function signature of get_module_name_from_crate to accept
a &Path instead of &PathBuf: update the signature in metadata.rs and any direct
callers to pass their Path/PathBuf (relying on deref coercion), and adjust any
documentation/comments referring to the parameter type; ensure imports remain
the same and run cargo build/tests to catch any remaining mismatched references
to get_module_name_from_crate.
- Around line 20-22: The Err arm that currently does eprintln!("{e}") should not
silently swallow parse failures; replace the eprintln call in that Err(e) branch
with logic that (1) records the error into a collection (e.g., push e or
(module_id, e) into a Vec<ParseError>) and (2) emits a structured log (e.g.,
tracing::error!(error = %e, "module parse failed")). Then update the surrounding
function's signature (the function that contains this match/Err branch) to
return either a Result with partial successes and an errors Vec or a
tuple/struct like (Vec<ModuleMetadata>, Vec<ParseError>) so callers can see
which modules failed; ensure any callers are updated to handle the new return
type and propagate or surface the collected errors instead of ignoring them.

In `@crates/module-parser/src/module_rs.rs`:
- Around line 62-102: The parse_module_args function currently ignores
non-string values for "name" and "deps" (and silently accepts bad tokens in
"capabilities"); update parse_module_args to validate and error on invalid types
instead of skipping: when parsing meta.path "name" or each entry in "deps" parse
the value to a Lit and if it is not Lit::Str return an anyhow::Error (use
anyhow::bail or context) that includes which key ("name" or "deps") and the
offending token; likewise, for "capabilities" ensure each entry parses to a
syn::Ident and bail with a clear message if not; return these errors so
ModuleInfo is only constructed when values are valid.

Signed-off-by: Bechma <19294519+Bechma@users.noreply.github.com>
Copy link
Copy Markdown
Contributor

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

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

🧹 Nitpick comments (1)
crates/cli/src/run/run_loop.rs (1)

313-324: Consider avoiding .unwrap() on path.parent().

While the current usage with relative paths like "src/main.rs" ensures parent() always returns Some, using unwrap() is fragile if future paths are added incorrectly.

♻️ Proposed improvement
 fn create_file_structure(path: &Path, relative_path: &str, contents: &str) -> anyhow::Result<()> {
     let path = PathBuf::from(path).join(BASE_PATH).join(relative_path);
-    fs::create_dir_all(path.parent().unwrap()).context("can't create directory")?;
+    if let Some(parent) = path.parent() {
+        fs::create_dir_all(parent).context("can't create directory")?;
+    }
     let mut file = fs::OpenOptions::new()
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@crates/cli/src/run/run_loop.rs` around lines 313 - 324, In
create_file_structure, avoid calling unwrap() on path.parent() — instead get the
parent directory into a variable (e.g., parent_dir) and convert the Option to a
Result with a clear error using ok_or_else or anyhow::bail so you return a
meaningful error when no parent exists; then call fs::create_dir_all(parent_dir)
and proceed to open/write the file. This removes the panic risk and keeps error
handling consistent with the existing .context(...) usage.
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Duplicate comments:
In `@crates/cli/src/run/run_loop.rs`:
- Around line 33-123: The watcher error handling in run() is already fixed—file
watcher errors are logged and the loop continues (see run(), fs_rx handling, and
cargo_run_loop usage), so no code changes are necessary; leave the event loop,
the eprintln! in the fs_rx recv error branch, and the signal_tx send behavior
as-is.

In `@crates/cli/src/run/templates.rs`:
- Around line 57-72: prepare_cargo_server_main already normalizes Windows
backslashes by replacing '\' with '/' and the crate-name hyphen-to-underscore
normalization is handled upstream in config.rs via package.replace("-", "_"), so
no functional change is needed; add a short inline comment inside
prepare_cargo_server_main referencing the upstream normalization (config.rs and
package.replace("-", "_")) to prevent future confusion and ensure reviewers know
hyphen handling is centralized.

In `@crates/module-parser/src/config.rs`:
- Around line 10-26: Duplicate detection for package keys is already implemented
in Config::create_dependencies: it checks module.metadata.package
(module.metadata.package.clone()), normalizes with package.replace("-", "_"),
and bails if dependencies.contains_key(&package) before inserting into
dependencies; no code changes required—keep the current logic in
create_dependencies as-is and proceed with the approval.

In `@crates/module-parser/src/module_rs.rs`:
- Around line 8-41: The crate root path should be derived from the package
manifest parent rather than the source file parent; in retrieve_module_rs
replace any use of src.parent() for the crate root with
package.manifest_path.parent().map(|p| p.display().to_string()) (as assigned to
crate_root) so ConfigModuleMetadata.path gets the manifest-derived path; locate
this logic in the retrieve_module_rs function and ensure
ConfigModuleMetadata.path uses the crate_root variable.

---

Nitpick comments:
In `@crates/cli/src/run/run_loop.rs`:
- Around line 313-324: In create_file_structure, avoid calling unwrap() on
path.parent() — instead get the parent directory into a variable (e.g.,
parent_dir) and convert the Option to a Result with a clear error using
ok_or_else or anyhow::bail so you return a meaningful error when no parent
exists; then call fs::create_dir_all(parent_dir) and proceed to open/write the
file. This removes the panic risk and keeps error handling consistent with the
existing .context(...) usage.

Signed-off-by: Bechma <19294519+Bechma@users.noreply.github.com>
Copy link
Copy Markdown
Contributor

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

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

Actionable comments posted: 1

🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Inline comments:
In `@crates/cli/src/run/run_loop.rs`:
- Around line 84-106: The code updates current_deps and watched_paths even when
generate_server_structure fails, preventing a future identical config from
retriggering generation; change the control flow so that after computing
new_deps (from get_config and create_dependencies) you attempt
generate_server_structure(&self.path, &self.config_path, &new_deps) and only on
its Ok path proceed to compute new_watched = collect_dep_paths(&new_deps,
&self.path), unwatch/watch accordingly, and assign watched_paths = new_watched
and current_deps = new_deps; on Err from generate_server_structure return/skip
updating state so the next config change can still trigger regeneration. Keep
references to generate_server_structure, collect_dep_paths, get_config,
create_dependencies, current_deps, watched_paths, and watcher.watch/unwatch.

Signed-off-by: Bechma <19294519+Bechma@users.noreply.github.com>
Copy link
Copy Markdown
Contributor

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

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

Actionable comments posted: 1

🧹 Nitpick comments (1)
crates/cli/src/run/run_loop.rs (1)

167-189: Coalesce bursty rerun signals to reduce restart thrash.
Multiple file events can queue many Reruns; draining extras before restarting avoids rapid kill/spawn loops.

♻️ Suggested refactor
-                Ok(RunSignal::Rerun) => {
-                    let _ = child.kill();
-                    let _ = child.wait();
-                    break true;
-                }
+                Ok(RunSignal::Rerun) => {
+                    // Drain extra reruns; honor a queued Stop.
+                    let mut stop = false;
+                    loop {
+                        match signal_rx.try_recv() {
+                            Ok(RunSignal::Rerun) => continue,
+                            Ok(RunSignal::Stop)
+                            | Err(mpsc::TryRecvError::Disconnected) => {
+                                stop = true;
+                                break;
+                            }
+                            Err(mpsc::TryRecvError::Empty) => break,
+                        }
+                    }
+                    let _ = child.kill();
+                    let _ = child.wait();
+                    if stop {
+                        return;
+                    }
+                    break true;
+                }
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@crates/cli/src/run/run_loop.rs` around lines 167 - 189, When handling
RunSignal::Rerun in the loop that sets rerun, coalesce bursty Rerun events by
draining any additional Rerun messages from signal_rx before killing/restarting
the child: inside the branch matching Ok(RunSignal::Rerun) (the block that
currently calls child.kill() and child.wait()), loop on signal_rx.try_recv() and
consume all Ok(RunSignal::Rerun) results (ignore Empty/Disconnected/Stop per
existing semantics), then perform the child.kill() and child.wait() once and
break true; reference symbols: signal_rx.try_recv(), RunSignal::Rerun,
child.kill(), child.wait(), and the rerun loop.
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Inline comments:
In `@crates/cli/src/run/run_loop.rs`:
- Around line 64-67: The current watch call watches the file path
(watcher.watch(&self.config_path, ...)) which misses atomic-rename saves; change
it to watch the config file's parent directory instead by computing the parent
directory of self.config_path (falling back to self.config_path if no parent)
and pass that directory to watcher.watch with RecursiveMode::NonRecursive,
keeping the existing .context("failed to watch config file")? error handling and
message.

---

Nitpick comments:
In `@crates/cli/src/run/run_loop.rs`:
- Around line 167-189: When handling RunSignal::Rerun in the loop that sets
rerun, coalesce bursty Rerun events by draining any additional Rerun messages
from signal_rx before killing/restarting the child: inside the branch matching
Ok(RunSignal::Rerun) (the block that currently calls child.kill() and
child.wait()), loop on signal_rx.try_recv() and consume all Ok(RunSignal::Rerun)
results (ignore Empty/Disconnected/Stop per existing semantics), then perform
the child.kill() and child.wait() once and break true; reference symbols:
signal_rx.try_recv(), RunSignal::Rerun, child.kill(), child.wait(), and the
rerun loop.

Signed-off-by: Bechma <19294519+Bechma@users.noreply.github.com>
Signed-off-by: Bechma <19294519+Bechma@users.noreply.github.com>
Copy link
Copy Markdown
Contributor

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

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

🧹 Nitpick comments (3)
crates/cli/src/run/run_loop.rs (3)

277-322: Required dependencies silently overwrite user-specified versions.

HashMap::insert replaces any existing entry. If a user's config specifies tokio or anyhow with different features or versions, those are silently overwritten. This could cause unexpected behavior if the user relies on specific configurations.

Consider using entry().or_insert() to preserve user-specified deps, or document that these dependencies are managed by the framework and must not be overridden.

💡 Alternative using entry API
-    dependencies.insert(
-        "tokio".to_owned(),
-        ConfigModuleMetadata {
-            package: Some("tokio".to_owned()),
-            features: vec!["full".to_owned()],
-            version: Some("1".to_owned()),
-            ..Default::default()
-        },
-    );
+    dependencies.entry("tokio".to_owned()).or_insert(ConfigModuleMetadata {
+        package: Some("tokio".to_owned()),
+        features: vec!["full".to_owned()],
+        version: Some("1".to_owned()),
+        ..Default::default()
+    });

Apply similarly to other required dependencies, or if overwriting is intentional, add a comment explaining why.

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@crates/cli/src/run/run_loop.rs` around lines 277 - 322, The code currently
uses HashMap::insert to add framework-required deps which silently overwrite
user-specified entries; update the logic that populates the dependencies map
(e.g., where required deps like "tokio" and "anyhow" are added—look for
variables such as required_deps, required_dependencies, or deps_map in
run_loop.rs or the run_loop function) to use the entry API
(entry().or_insert(...) or entry().or_insert_with(...)) so existing
user-provided versions/features are preserved, or explicitly check contains_key
and only insert when absent; apply the same change to all places adding
framework-managed deps or add a clear comment if overwriting is intentional.

111-125: Clarify the restart-on-watcher-error strategy.

On watcher unwatch/watch failures, the code sends Stop to the runner but returns Ok(RunSignal::Rerun). This appears intentional to trigger a full restart at the outer level, but the mixed signals are confusing without explanation.

Consider adding a brief comment explaining why a full restart is preferred over continuing with a degraded watch set.

💡 Suggested clarification
                                for old in watched_paths.difference(&new_watched) {
                                    if let Err(err) = watcher.unwatch(old) {
                                        eprintln!("failed to unwatch {old:?}: {err}");
+                                       // Watcher state is inconsistent; trigger full restart
                                        _ = signal_tx.send(RunSignal::Stop);
                                        runner_handle.join().expect("runner thread panicked");
                                        return Ok(RunSignal::Rerun);
                                    }
                                }
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@crates/cli/src/run/run_loop.rs` around lines 111 - 125, The watcher error
handling is sending a Stop to the runner but also returning
Ok(RunSignal::Rerun), which is intentional but unclear; add a brief comment in
run_loop.rs at the block that sends the Stop (where runner_tx.send(Stop) or
similar is called and RunSignal::Rerun is returned) explaining that on
watch/unwatch failures we prefer a full restart (Stop the current runner and ask
the outer loop to recreate watchers and the runner) rather than continuing with
a degraded watcher set to ensure consistent state and avoid concurrent runners
or partial watches.

252-268: Prefer &Path over &PathBuf for function parameters.

Using &PathBuf instead of &Path is a Rust anti-pattern. &Path accepts both &Path and &PathBuf (via Deref), providing greater flexibility for callers.

♻️ Suggested refactor
-fn get_config(path: &PathBuf, config_path: &PathBuf) -> anyhow::Result<Config> {
+fn get_config(path: &Path, config_path: &Path) -> anyhow::Result<Config> {
     let mut config = get_config_from_path(config_path)?;
     let mut members = get_module_name_from_crate(path)?;
     // ...
 }

-fn get_config_from_path(path: &PathBuf) -> anyhow::Result<Config> {
+fn get_config_from_path(path: &Path) -> anyhow::Result<Config> {
     let config = fs::File::open(path).context("config not available")?;
     serde_saphyr::from_reader(config).context("config not valid")
 }
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@crates/cli/src/run/run_loop.rs` around lines 252 - 268, Several functions in
run_loop.rs use &PathBuf for parameters, which is an anti-pattern; change their
parameter types to &Path (from std::path::Path) so callers can pass either &Path
or &PathBuf. For each function signature that currently takes &PathBuf (look for
occurrences in run_loop.rs), replace &PathBuf with &Path, add use
std::path::Path if missing, and update call sites to pass the same reference (no
cloning needed); ensure any pattern matches or methods that require PathBuf are
adjusted (e.g., convert to owned PathBuf only where ownership is required by
calling .to_path_buf()).
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Nitpick comments:
In `@crates/cli/src/run/run_loop.rs`:
- Around line 277-322: The code currently uses HashMap::insert to add
framework-required deps which silently overwrite user-specified entries; update
the logic that populates the dependencies map (e.g., where required deps like
"tokio" and "anyhow" are added—look for variables such as required_deps,
required_dependencies, or deps_map in run_loop.rs or the run_loop function) to
use the entry API (entry().or_insert(...) or entry().or_insert_with(...)) so
existing user-provided versions/features are preserved, or explicitly check
contains_key and only insert when absent; apply the same change to all places
adding framework-managed deps or add a clear comment if overwriting is
intentional.
- Around line 111-125: The watcher error handling is sending a Stop to the
runner but also returning Ok(RunSignal::Rerun), which is intentional but
unclear; add a brief comment in run_loop.rs at the block that sends the Stop
(where runner_tx.send(Stop) or similar is called and RunSignal::Rerun is
returned) explaining that on watch/unwatch failures we prefer a full restart
(Stop the current runner and ask the outer loop to recreate watchers and the
runner) rather than continuing with a degraded watcher set to ensure consistent
state and avoid concurrent runners or partial watches.
- Around line 252-268: Several functions in run_loop.rs use &PathBuf for
parameters, which is an anti-pattern; change their parameter types to &Path
(from std::path::Path) so callers can pass either &Path or &PathBuf. For each
function signature that currently takes &PathBuf (look for occurrences in
run_loop.rs), replace &PathBuf with &Path, add use std::path::Path if missing,
and update call sites to pass the same reference (no cloning needed); ensure any
pattern matches or methods that require PathBuf are adjusted (e.g., convert to
owned PathBuf only where ownership is required by calling .to_path_buf()).

@Bechma Bechma merged commit f875a7f into cyberfabric:main Feb 18, 2026
1 check passed
@Bechma Bechma deleted the setup branch February 18, 2026 15:27
@coderabbitai coderabbitai bot mentioned this pull request Mar 13, 2026
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant