Skip to content

Add foreach target expansion and value-level Jinja rendering#123

Merged
leynos merged 17 commits intomainfrom
codex/implement-dynamic-features-for-custom-functions
Aug 18, 2025
Merged

Add foreach target expansion and value-level Jinja rendering#123
leynos merged 17 commits intomainfrom
codex/implement-dynamic-features-for-custom-functions

Conversation

@leynos
Copy link
Copy Markdown
Owner

@leynos leynos commented Aug 16, 2025

Summary

  • parse manifests as plain YAML before templating
  • add foreach/when expansion and render Jinja only in value fields
  • document YAML-first pipeline and mark roadmap item as complete

Testing

  • make fmt
  • make check-fmt
  • make lint
  • make test
  • make markdownlint
  • make nixie (fails: Error running command bun x --bun @mermaid-js/mermaid-cli ...)

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

Summary by Sourcery

Parse manifests as YAML before templating, implement foreach/when target expansion, and restrict Jinja evaluation to value-level string fields.

New Features:

  • Add foreach and when keys to generate targets dynamically with item and index variables.
  • Enable Jinja rendering only within string values after deserialization.

Enhancements:

  • Refactor manifest parsing pipeline to perform YAML parsing first, then expand foreach, then render templated values.
  • Update examples and AST handling to accommodate YAML-first and value-level rendering.

Documentation:

  • Revise design documentation to describe the three-stage YAML-first pipeline and remove the legacy two-pass mechanism.
  • Mark related roadmap items as complete.

Tests:

  • Add and update tests for foreach expansion, when filtering, and related error cases in parsing.

Chores:

  • Update example manifest files to new foreach syntax.

@sourcery-ai
Copy link
Copy Markdown
Contributor

sourcery-ai Bot commented Aug 16, 2025

Reviewer's Guide

This PR overhauls manifest ingestion into a YAML-first pipeline with value-level Jinja rendering, introduces a dedicated foreach/when expansion pass, and aligns documentation, tests, and examples with the new approach.

Flow diagram for the new YAML-first manifest ingestion pipeline

flowchart TD
    A[Parse manifest as YAML] --> B[Expand foreach/when keys]
    B --> C[Deserialize to NetsukeManifest AST]
    C --> D[Render Jinja expressions in value fields]
    D --> E[Ready for execution]
Loading

Flow diagram for foreach/when target expansion

flowchart TD
    A[Iterate over targets in YAML] --> B{Has foreach?}
    B -- Yes --> C[Evaluate foreach expression]
    C --> D[For each item:]
    D --> E{Has when?}
    E -- Yes --> F[Evaluate when expression]
    F -- True --> G[Expand target with item/index]
    F -- False --> H[Skip target]
    E -- No --> G
    B -- No --> I[Keep target as is]
Loading

File-Level Changes

Change Details Files
Adopt a YAML-first parsing approach and limit Jinja evaluation to string fields
  • Parse raw manifest into serde_yml::Value before any templating
  • Remove text-based first and second Jinja renders
  • Introduce render_manifest, render_vars, and render_string_or_list for field-level templating
src/manifest.rs
Implement foreach/when expansion in raw YAML targets
  • Add expand_foreach to iterate targets based on a Jinja expression
  • Inject item and index into per-iteration vars and apply when filters
  • Invoke expand_foreach in from_str before deserialization
src/manifest.rs
Update documentation to describe the YAML-first pipeline and mark roadmap items complete
  • Revise netsuke-design.md to outline the three-stage ingestion process
  • Mark removal of first-pass parsing and foreach/when implementation as done in roadmap.md
docs/netsuke-design.md
docs/roadmap.md
Revise tests and examples to use the new foreach syntax
  • Replace Jinja loop tests with foreach-based scenarios and add invalid-case tests
  • Update feature specs to reference foreach fixtures
  • Adjust examples to drop template braces around foreach expressions
tests/manifest_jinja_tests.rs
tests/features/manifest.feature
tests/data/foreach.yml
tests/data/foreach_invalid.yml
examples/photo_edit.yml
examples/visual_design.yml
examples/website.yml
examples/writing.yml

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 Aug 16, 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.

Warning

Rate limit exceeded

@leynos has exceeded the limit for the number of commits or files that can be reviewed per hour. Please wait 1 minutes and 5 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 8faff59 and eaad737.

📒 Files selected for processing (6)
  • docs/netsuke-design.md (10 hunks)
  • examples/writing.yml (1 hunks)
  • src/manifest.rs (1 hunks)
  • tests/features/manifest.feature (1 hunks)
  • tests/manifest_jinja_tests.rs (5 hunks)
  • tests/steps/manifest_steps.rs (5 hunks)

Summary by CodeRabbit

  • New Features

    • YAML-first manifest processing with multi-stage ingestion.
    • Support for foreach/when target generation with item and index context.
    • Rendering limited to recognised string fields; structural templating removed.
  • Documentation

    • Redesigned ingestion narrative; consistent British spelling.
    • Added diagrams and clarified terminology.
  • Examples

    • Updated sample manifests to use bare expressions for foreach.
  • Tests

    • Expanded scenarios covering foreach/when, field rendering, and error cases; removed legacy Jinja loop tests.
  • Chores

    • Roadmap updated to reflect completed YAML-first and foreach/when work.

Walkthrough

Summarise the YAML-first, multi-stage ingestion: parse YAML → expand foreach/when into per-item targets (inject item and index) → deserialize into the public NetsukeManifest AST → render remaining templated string fields per-field. Update docs, examples and tests; keep public API signatures unchanged.

Changes

Cohort / File(s) Summary
Design Docs
docs/netsuke-design.md
Rewrite to a YAML-first, multi-stage ingestion; expose explicit public AST declarations and Rust sketch; rename sections to British spellings and emphasise deserialisation-first flow; add mermaid expansion diagram.
Roadmap
docs/roadmap.md
Mark removal of global first-pass Jinja and foreach/when implementation as completed; update feature statuses.
AST Declarations
src/ast.rs
Add public AST types: NetsukeManifest, Rule, Recipe (untagged), Target, StringOrList with serde attributes and explicit field signatures.
Manifest Core
src/manifest.rs
Replace two‑pass Jinja flow with YAML-first pipeline: parse YAML, load strict globals, expand foreach/when into per-item targets, deserialize to AST, then render templated string fields; add expansion/rendering helpers, error constants and stricter var handling; retain public API signatures.
Examples
examples/photo_edit.yml, examples/visual_design.yml, examples/website.yml, examples/writing.yml
Replace moustache-wrapped foreach values with bare expressions (e.g., glob(...)) so foreach is provided as an expression rather than a templated string.
Tests — Feature & Unit
tests/features/manifest.feature, tests/manifest_jinja_tests.rs
Replace Jinja-loop scenarios with foreach scenarios; add tests for foreach expansion, filtering, invalid foreach, and per-target field rendering (command, script, rule); add helpers for StringOrList assertions and target extractors.
Tests — Cucumber Steps
tests/steps/manifest_steps.rs
Add helpers to fetch targets and assert sources, deps, order-only deps, script and rule; add assert_list_contains utility and adapt existing name/command checks to use helpers.
Test Data Added
tests/data/foreach.yml, tests/data/foreach_invalid.yml, tests/data/render_target.yml
Add fixtures for valid foreach, invalid (non-iterable) foreach, and comprehensive target-field rendering.
Test Data Removed
tests/data/jinja_for.yml, tests/data/jinja_for_invalid.yml
Remove legacy Jinja-loop fixtures.

Sequence Diagram(s)

sequenceDiagram
  participant Caller
  participant Manifest as manifest.rs
  participant YAML as YAML Parser
  participant Exp as Foreach/When Expander
  participant Serde as Deserialiser
  participant Render as Renderer

  Caller->>Manifest: from_path / from_str(yaml)
  Manifest->>YAML: Parse YAML to YamlValue
  YAML-->>Manifest: YamlValue
  Manifest->>Exp: Expand foreach + when → per-item target entries (inject item/index)
  Exp-->>Manifest: Expanded YamlValue
  Manifest->>Serde: Deserialize to NetsukeManifest (AST)
  Serde-->>Manifest: NetsukeManifest
  Manifest->>Render: Render string fields per-target (vars, targets, rules)
  Render-->>Manifest: Rendered NetsukeManifest
  Manifest-->>Caller: NetsukeManifest
Loading

Estimated code review effort

🎯 4 (Complex) | ⏱️ ~60 minutes

Poem

Parse the YAML, then unroll the list,
Foreach steps march where Jinja missed.
Inject item and index, bind each local set,
Deserialise to AST, then render the text.
Targets stride out tidy, data-first, all set.

✨ 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-dynamic-features-for-custom-functions

🪧 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.
    • 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.
  • 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 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/Issue comments)

Type @coderabbitai help to get the list of available commands.

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

Status, Documentation and Community

  • Visit our Status Page to check the current availability of CodeRabbit.
  • 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.

codescene-delta-analysis[bot]

This comment was marked as outdated.

sourcery-ai[bot]

This comment was marked as resolved.

codescene-delta-analysis[bot]

This comment was marked as 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

🔭 Outside diff range comments (5)
examples/writing.yml (1)

23-23: Declare deps as a list to avoid type ambiguity.

If deps accepts multiple entries, keep the type consistent by always using a list, even for singletons. This reduces downstream branching and future-proofs the schema.

-    deps: "{{ build_dir }}"
+    deps:
+      - "{{ build_dir }}"
docs/netsuke-design.md (2)

1074-1104: Fix broken fenced code blocks for Ninja snippets

The Ninja examples have mis-nested/duplicated fences, which breaks Markdown rendering. Close each code block once, and avoid stray “```ninja” lines.

Proposed corrected snippet structure:

```ninja
# Generated from an ir::Action
rule cc
  command = gcc -c -o $out $in
  description = CC $out
  depfile = $out.d
  deps = gcc
# Generated from an ir::BuildEdge
build foo.o: cc foo.c
build bar.o: cc bar.c
build my_app: link foo.o bar.o | lib_dependency.a

Run “make markdownlint” and “make fmt” to normalise fences and wrapping.

I can sweep the doc for fence issues and fix them in one pass.

---

`1610-1699`: **Repair broken footnotes/links and normalise citation syntax**

Several citations display malformed URLs (e.g., stray “<<<”, broken anchors). This will fail markdownlint and is confusing to readers.



- Replace malformed links with valid Markdown links.
- Keep footnotes as GitHub-flavoured Markdown footnotes, ordered by first appearance.
- Wrap paragraphs at 80 columns.

Offer: I can produce a cleaned “Works cited” section adhering to the house style.

</blockquote></details>
<details>
<summary>tests/features/manifest.feature (1)</summary><blockquote>

`85-95`: **Broaden assertions to cover phony/always flags and rule exclusivity**

The “Rendering all target fields” scenario is strong. Add checks for:
- phony/always defaults on targets 1–3
- exclusivity enforcement across rule/command/script (e.g., a dedicated failure scenario)


I can extend the feature and add a small invalid manifest fixture to test mutual exclusivity errors.

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

`66-92`: **Use `rstest` parameterisation to reduce duplication in foreach tests.**

The `expands_foreach_targets` and `foreach_when_filters_items` tests share similar logic. Use `rstest` parameterisation to combine them.



```diff
-#[rstest]
-fn expands_foreach_targets() {
-    let yaml = manifest_yaml(
-        "targets:\n  - foreach: \"['a', 'b']\"\n    name: '{{ item }}'\n    command: 'echo {{ item }}'\n",
-    );
-
-    let manifest = manifest::from_str(&yaml).expect("parse");
-    assert_eq!(manifest.targets.len(), 2);
-    let names: Vec<_> = manifest
-        .targets
-        .iter()
-        .map(|t| match &t.name {
-            netsuke::ast::StringOrList::String(s) => s.clone(),
-            other => panic!("Expected String, got: {other:?}"),
-        })
-        .collect();
-    assert_eq!(names, vec!["a", "b"]);
-
-    let commands: Vec<_> = manifest
-        .targets
-        .iter()
-        .map(|t| match &t.recipe {
-            Recipe::Command { command } => command.clone(),
-            other => panic!("Expected command recipe, got: {other:?}"),
-        })
-        .collect();
-    assert_eq!(commands, vec!["echo a", "echo b"]);
-}
+#[rstest]
+#[case(
+    "targets:\n  - foreach: \"['a', 'b']\"\n    name: '{{ item }}'\n    command: 'echo {{ item }}'\n",
+    vec!["a", "b"],
+    vec!["echo a", "echo b"]
+)]
+#[case(
+    "targets:\n  - foreach: \"['a', 'skip', 'b']\"\n    when: item != 'skip'\n    name: '{{ item }}'\n    command: 'echo {{ item }}'\n",
+    vec!["a", "b"],
+    vec!["echo a", "echo b"]
+)]
+fn expands_foreach_targets(
+    #[case] yaml_body: &str,
+    #[case] expected_names: Vec<&str>,
+    #[case] expected_commands: Vec<&str>,
+) {
+    let yaml = manifest_yaml(yaml_body);
+
+    let manifest = manifest::from_str(&yaml).expect("parse");
+    assert_eq!(manifest.targets.len(), expected_names.len());
+    
+    let names: Vec<_> = manifest
+        .targets
+        .iter()
+        .map(|t| match &t.name {
+            netsuke::ast::StringOrList::String(s) => s.clone(),
+            other => panic!("Expected String, got: {other:?}"),
+        })
+        .collect();
+    assert_eq!(names, expected_names);
+
+    let commands: Vec<_> = manifest
+        .targets
+        .iter()
+        .map(|t| match &t.recipe {
+            Recipe::Command { command } => command.clone(),
+            other => panic!("Expected command recipe, got: {other:?}"),
+        })
+        .collect();
+    assert_eq!(commands, expected_commands);
+}

Also applies to: 140-156

♻️ Duplicate comments (4)
src/manifest.rs (4)

48-63: Split expand_foreach into smaller functions to reduce complexity.

The expand_foreach function's cyclomatic complexity can be reduced by extracting the target expansion logic into a separate helper function.

 fn expand_foreach(doc: &mut YamlValue, env: &Environment) -> Result<()> {
+    expand_foreach_targets(doc, env)
+}
+
+fn expand_foreach_targets(doc: &mut YamlValue, env: &Environment) -> Result<()> {
     let Some(targets) = doc.get_mut("targets").and_then(|v| v.as_sequence_mut()) else {
         return Ok(());
     };
 
     let mut expanded = Vec::new();
     for target in std::mem::take(targets) {
         match target {
             YamlValue::Mapping(map) => expanded.extend(expand_target(map, env)?),
             other => expanded.push(other),
         }
     }
 
     *targets = expanded;
     Ok(())
 }

65-83: Extract conditional logic in expand_target to improve readability.

The expand_target function contains nested conditional logic that could be simplified by extracting helper predicates.

+fn has_foreach_key(map: &YamlMapping) -> bool {
+    map.contains_key(&YamlValue::String("foreach".into()))
+}
+
+fn process_foreach_item(
+    map: &YamlMapping,
+    env: &Environment,
+    item: &Value,
+    index: usize,
+) -> Result<Option<YamlValue>> {
+    let mut clone = map.clone();
+    clone.remove(&YamlValue::String("foreach".into()));
+    if !when_allows(&mut clone, env, item, index)? {
+        return Ok(None);
+    }
+    inject_iteration_vars(&mut clone, item, index)?;
+    Ok(Some(YamlValue::Mapping(clone)))
+}
+
 fn expand_target(map: YamlMapping, env: &Environment) -> Result<Vec<YamlValue>> {
-    let foreach_key = YamlValue::String("foreach".into());
-    if let Some(expr_val) = map.get(&foreach_key) {
+    if has_foreach_key(&map) {
+        let expr_val = map.get(&YamlValue::String("foreach".into())).expect("foreach exists");
         let values = parse_foreach_values(expr_val, env)?;
         let mut items = Vec::new();
         for (index, item) in values.into_iter().enumerate() {
-            let mut clone = map.clone();
-            clone.remove(&foreach_key);
-            if !when_allows(&mut clone, env, &item, index)? {
-                continue;
+            if let Some(target) = process_foreach_item(&map, env, &item, index)? {
+                items.push(target);
             }
-            inject_iteration_vars(&mut clone, &item, index)?;
-            items.push(YamlValue::Mapping(clone));
         }
         Ok(items)
     } else {
         Ok(vec![YamlValue::Mapping(map)])
     }
 }

181-201: Add unit tests for render_target to ensure all fields are rendered correctly.

The render_target function handles rendering of multiple fields and recipe variants but lacks dedicated unit tests. Add comprehensive tests to verify correct rendering and error handling.

Would you like me to generate comprehensive unit tests for the render_target function that cover all fields and recipe variants?


13-14: Extract repeated error context strings into constants.

Multiple error context strings are repeated throughout the code. Extract these into named constants to improve maintainability.

 const ERR_INITIAL_YAML_PARSE: &str = "initial YAML parse error";
 const ERR_MANIFEST_PARSE: &str = "manifest parse error";
+const ERR_RENDER_RULE_DESC: &str = "render rule description";
+const ERR_RENDER_RULE_CMD: &str = "render rule command";
+const ERR_RENDER_RULE_SCRIPT: &str = "render rule script";
+const ERR_RENDER_TARGET_CMD: &str = "render target command";
+const ERR_RENDER_TARGET_SCRIPT: &str = "render target script";
+const ERR_RENDER_STRING: &str = "render string value";
+const ERR_RENDER_LIST: &str = "render list value";
+const ERR_FOREACH_PARSE: &str = "foreach expression parse error";
+const ERR_FOREACH_EVAL: &str = "foreach evaluation error";
+const ERR_WHEN_PARSE: &str = "when expression parse error";
+const ERR_WHEN_EVAL: &str = "when evaluation error";

 fn as_str<'a>(value: &'a YamlValue, field: &str) -> Result<&'a str> {
     value
         .as_str()
         .with_context(|| format!("{field} must be a string expression"))
 }

 fn eval_expression(env: &Environment, name: &str, expr: &str, ctx: Value) -> Result<Value> {
+    let parse_err = match name {
+        "foreach" => ERR_FOREACH_PARSE,
+        "when" => ERR_WHEN_PARSE,
+        _ => "expression parse error",
+    };
+    let eval_err = match name {
+        "foreach" => ERR_FOREACH_EVAL,
+        "when" => ERR_WHEN_EVAL,
+        _ => "evaluation error",
+    };
     env.compile_expression(expr)
-        .with_context(|| format!("{name} expression parse error"))?
+        .context(parse_err)?
         .eval(ctx)
-        .with_context(|| format!("{name} evaluation error"))
+        .context(eval_err)
 }

Then update the render functions:

 fn render_rule(rule: &mut crate::ast::Rule, env: &Environment) -> Result<()> {
     if let Some(desc) = &mut rule.description {
         *desc = env
             .render_str(desc, context! {})
-            .context("render rule description")?;
+            .context(ERR_RENDER_RULE_DESC)?;
     }
     render_string_or_list(&mut rule.deps, env, &Vars::new())?;
     match &mut rule.recipe {
         Recipe::Command { command } => {
             *command = env
                 .render_str(command, context! {})
-                .context("render rule command")?;
+                .context(ERR_RENDER_RULE_CMD)?;
         }
         Recipe::Script { script } => {
             *script = env
                 .render_str(script, context! {})
-                .context("render rule script")?;
+                .context(ERR_RENDER_RULE_SCRIPT)?;
         }
         Recipe::Rule { rule: r } => render_string_or_list(r, env, &Vars::new())?,
     }
     Ok(())
 }

Also applies to: 133-134, 138-141, 161-162, 168-169, 173-174, 189-190, 195-196, 208-209, 218-218, 222-222

📜 Review details

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

💡 Knowledge Base configuration:

  • Jira integration is disabled

You can enable these sources in your CodeRabbit configuration.

📥 Commits

Reviewing files that changed from the base of the PR and between 1280df6 and fbe646d.

📒 Files selected for processing (15)
  • docs/netsuke-design.md (2 hunks)
  • docs/roadmap.md (1 hunks)
  • examples/photo_edit.yml (1 hunks)
  • examples/visual_design.yml (1 hunks)
  • examples/website.yml (1 hunks)
  • examples/writing.yml (1 hunks)
  • src/manifest.rs (1 hunks)
  • tests/data/foreach.yml (1 hunks)
  • tests/data/foreach_invalid.yml (1 hunks)
  • tests/data/jinja_for.yml (0 hunks)
  • tests/data/jinja_for_invalid.yml (0 hunks)
  • tests/data/render_target.yml (1 hunks)
  • tests/features/manifest.feature (1 hunks)
  • tests/manifest_jinja_tests.rs (3 hunks)
  • tests/steps/manifest_steps.rs (2 hunks)
💤 Files with no reviewable changes (2)
  • tests/data/jinja_for_invalid.yml
  • tests/data/jinja_for.yml
🧰 Additional context used
📓 Path-based instructions (3)
**/*.md

📄 CodeRabbit Inference Engine (AGENTS.md)

**/*.md: Markdown files must use en-GB-oxendict ("-ize" / "-yse" / "-our") spelling and grammar. (EXCEPTION: the naming of the "LICENSE" file, which is to be left unchanged for community consistency.)
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 must be wrapped at 120 columns.
Tables and headings must not be wrapped.
Use dashes (-) for list bullets.
Use GitHub-flavoured Markdown footnotes ([^1]) for references and footnotes.

Files:

  • docs/roadmap.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
  • Headings must not be wrapped.
  • Documents must start with a level 1 heading
  • Headings must correctly increase or decrease by no more than one level at a time
  • Use GitHub-flavoured Markdown style for footnotes and endnotes.
  • Numbered footnotes must be numbered by order of appearance in the document.

Files:

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

📄 CodeRabbit Inference Engine (AGENTS.md)

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

Files:

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

📄 CodeRabbit Inference Engine (AGENTS.md)

**/*.rs: Comment why, not what. Explain assumptions, edge cases, trade-offs, or complexity. Don't echo the obvious.
Use functions and composition. Avoid repetition by extracting reusable logic. Prefer generators or comprehensions, and declarative code to imperative repetition when readable.
Small, meaningful functions. Functions must be small, clear in purpose, single responsibility, and obey command/query segregation.
Name things precisely. Use clear, descriptive variable and function names. For booleans, prefer names with is, has, or should.
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.
Place function attributes after doc comments.
Do not use return in single-line functions.
Prefer immutable data and avoid unnecessary mut bindings.
Handle errors with the Result type instead of panicking where feasible.
Prefer .expect() over .unwrap().
Use concat!() to combine long string literals rather than escaping newlines with a backslash.
Prefer single line versions of functions where appropriate.
Clippy warnings MUST be disallowed.
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.
Keep file size manageable. No single code file may be longer than 400 lines. Long switch statements or dispatch tables should be broken up by feature and constituents colocated with targets. Large blocks of test data should be moved to external data files.
Illustrate with clear examples. Function documentation must include clear examples demonstrating the usage and outcome of the function. Test documentation should omit examples where the example serves only to reiterate the test logic.
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.
...

Files:

  • tests/steps/manifest_steps.rs
  • src/manifest.rs
  • tests/manifest_jinja_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.
  • Where code is only used by specific features, it must be conditionally compiled or a conditional expectation for unused_code applied.

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

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

  • Prefer mockall for mocks/stubs.

  • Prefer .expect() over .unwrap()

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

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

  • Files must not exceed 400 lines in length

    • Large modules must be decomposed
    • Long match statements or dispatch tables should be decomposed by domain and collocated with targets
    • Large blocks of inline data (e.g., test fixtures, constants or templates) must be moved to external files and inlined at compile-time or loaded at run-time.

Files:

  • tests/steps/manifest_steps.rs
  • src/manifest.rs
  • tests/manifest_jinja_tests.rs
🧬 Code Graph Analysis (1)
tests/manifest_jinja_tests.rs (2)
src/manifest.rs (1)
  • from_str (24-45)
src/runner.rs (1)
  • is_empty (96-98)
🪛 LanguageTool
docs/roadmap.md

[uncategorized] ~93-~93: Use a comma before ‘so’ if it connects two independent clauses (unless they are closely connected and short).
Context: ...move the global first-pass Jinja parsing so manifests are valid YAML before...

(COMMA_COMPOUND_SENTENCE_2)


[uncategorized] ~93-~93: Possible missing preposition found.
Context: ...ss Jinja parsing so manifests are valid YAML before any templating occurs. ...

(AI_HYDRA_LEO_MISSING_TO)

🔍 Remote MCP

Summary of Relevant Context for PR #123 Review:

  1. Design Documentation Changes

    • docs/netsuke-design.md updated to describe a data-first pipeline:
      • YAML parsed before any templating.
      • Three stages: template expansion (foreach/when), deserialization, final Jinja rendering on string fields. [ai_summary]
    • docs/roadmap.md marks removal of global first-pass Jinja parsing done, and implementation of foreach/when. [ai_summary]
  2. Implementation Changes

    • src/manifest.rs now implements single-pass YAML-first flow:
      • parse YAML via from_str, expand foreach and evaluate when, then render remaining Jinja in strings. [ai_summary]
    • No new public API signatures; existing from_str and from_path remain. [ask_question::list_public_api_functions]
    • No ERR_* constants; error handling uses anyhow::Context. [ask_question::error_constants]
    • No new functions in src/manifest.rs related to foreach; logic moved to Jinja pass outside this file. [ask_question::new_functions]
  3. Examples and Tests

    • Updated examples use bare expressions for foreach instead of templated strings. [ai_summary]
    • Tests in tests/data/ and tests/features/ updated: removed Jinja-based for tests, added foreach-based scenarios. [ai_summary]
    • New Cucumber steps assert target sources, deps, order-only_deps, scripts, rules. [ai_summary]
    • IrGenError variants remain unchanged: RuleNotFound, MultipleRules, DuplicateOutput in src/ir.rs. [deepwiki]

No further issues found within iteration limits.

⏰ Context from checks skipped due to timeout of 120000ms. You can increase the timeout in your CodeRabbit configuration to a maximum of 15 minutes (900000ms). (1)
  • GitHub Check: build-test
🔇 Additional comments (8)
examples/website.yml (1)

17-17: LGTM: adopt YAML-first foreach expression.

This aligns with the new pipeline: expand foreach on data, then render strings. Keep it.

examples/visual_design.yml (1)

16-16: LGTM: migrate foreach to a bare expression.

This matches the documented YAML-first, per-field rendering pipeline.

examples/photo_edit.yml (1)

17-21: Adopt bare Jinja expression for foreach; verify glob() availability

Ensure the Jinja glob() function is exposed in your pipeline before using it in examples/photo_edit.yml (lines 17–21); otherwise expansion will fail at example-run time. Add a guard in your CI to skip this example until glob() support lands, or rewrite the example to use a static file list for CI portability.

  • File: examples/photo_edit.yml
  • Lines: 17–21
docs/roadmap.md (1)

99-103: LGTM: Foreach/when status and semantics updated

Marking foreach/when as implemented with item/index and precedence layering matches the implementation and tests.

tests/data/render_target.yml (2)

10-18: Nice coverage of per-target rendering

Exercising name, sources, deps, order_only_deps, and command in one target is a good end-to-end check for the final rendering pass.


8-11: Ignore unquoting suggestion

The foreach handler calls parse_foreach_values, which starts with as_str(expr_val, "foreach") and only accepts a string. Quoting "[1]" is required so that eval_expression can parse it as a list literal. A bare YAML sequence (foreach: [1]) would fail at as_str and break iteration.

No changes needed in tests/data/render_target.yml.

Likely an incorrect or invalid review comment.

docs/netsuke-design.md (1)

596-600: Ignore stage numbering update
Stage numbering is consistent: Stage 5 is “IR Generation & Validation” in both the diagram and Section 1.2. No changes required.

Likely an incorrect or invalid review comment.

tests/features/manifest.feature (1)

80-84: Negative test for non-iterable foreach is appropriate

Keep this to guard error messaging and type-checking in foreach evaluation.

Comment thread docs/netsuke-design.md
Comment thread docs/roadmap.md Outdated
Comment thread examples/visual_design.yml
Comment thread examples/website.yml
Comment thread examples/writing.yml
Comment thread tests/data/foreach.yml Outdated
Comment thread tests/features/manifest.feature
Comment thread tests/manifest_jinja_tests.rs
Comment thread tests/steps/manifest_steps.rs
Comment thread tests/steps/manifest_steps.rs
Comment thread tests/manifest_jinja_tests.rs Outdated
codescene-delta-analysis[bot]

This comment was marked as 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: 4

🔭 Outside diff range comments (2)
tests/manifest_jinja_tests.rs (2)

1-1: Update module docs to reflect YAML-first, value-level rendering.

Align the module header with the new three-stage pipeline.

-//! Tests for Jinja-templated manifest parsing.
+//! Tests for the YAML-first manifest pipeline: parse YAML, expand foreach/when, then render Jinja only in string values.

66-92: Add coverage for the index variable exposed by foreach.

Exercise index to lock down its availability and ordering semantics.

Example new test:

#[rstest]
fn foreach_exposes_index() {
    let yaml = manifest_yaml(
        "targets:\n  - foreach: \"['x', 'y']\"\n    name: '{{ index }}:{{ item }}'\n    command: 'echo {{ index }} {{ item }}'\n",
    );

    let manifest = manifest::from_str(&yaml).expect("parse");
    let names: Vec<_> = manifest
        .targets
        .iter()
        .map(|t| match &t.name {
            netsuke::ast::StringOrList::String(s) => s.clone(),
            other => panic!("Expected String, got: {other:?}"),
        })
        .collect();
    assert_eq!(names, vec!["0:x", "1:y"]);

    let commands: Vec<_> = manifest
        .targets
        .iter()
        .map(|t| match &t.recipe {
            Recipe::Command { command } => command.clone(),
            other => panic!("Expected command recipe, got: {other:?}"),
        })
        .collect();
    assert_eq!(commands, vec!["echo 0 x", "echo 1 y"]);
}
♻️ Duplicate comments (1)
tests/manifest_jinja_tests.rs (1)

172-207: Deduplicate StringOrList assertions via helpers.

Extract repeated StringOrList pattern matches into helpers and use them here.

Add these helpers near the top of the file:

fn assert_string_or_list_eq(actual: &netsuke::ast::StringOrList, expected: &str, field: &str) {
    match actual {
        netsuke::ast::StringOrList::String(s) => assert_eq!(s, expected),
        netsuke::ast::StringOrList::List(list) if list.len() == 1 => {
            assert_eq!(&list[0], expected);
        }
        other => panic!("Expected String for {field}, got: {other:?}"),
    }
}

fn assert_string_or_list_contains(
    actual: &netsuke::ast::StringOrList,
    expected: &[String],
    field: &str,
) {
    match actual {
        netsuke::ast::StringOrList::List(list) => assert_eq!(list, expected),
        other => panic!("Expected List for {field}, got: {other:?}"),
    }
}

Then apply this diff in this test:

-    match &target.name {
-        netsuke::ast::StringOrList::String(n) => assert_eq!(n, "base1"),
-        other => panic!("Expected String, got: {other:?}"),
-    }
+    assert_string_or_list_eq(&target.name, "base1", "name");
 
-    match &target.sources {
-        netsuke::ast::StringOrList::List(list) => assert_eq!(list, &["base1.src".to_string()]),
-        other => panic!("Expected List, got: {other:?}"),
-    }
+    assert_string_or_list_contains(&target.sources, &["base1.src".to_string()], "sources");
 
-    match &target.deps {
-        netsuke::ast::StringOrList::List(list) => assert_eq!(list, &["base1.dep".to_string()]),
-        other => panic!("Expected List, got: {other:?}"),
-    }
+    assert_string_or_list_contains(&target.deps, &["base1.dep".to_string()], "deps");
 
-    match &target.order_only_deps {
-        netsuke::ast::StringOrList::List(list) => {
-            assert_eq!(list, &["base1.ord".to_string()]);
-        }
-        other => panic!("Expected List, got: {other:?}"),
-    }
+    assert_string_or_list_contains(
+        &target.order_only_deps,
+        &["base1.ord".to_string()],
+        "order_only_deps",
+    );
📜 Review details

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

💡 Knowledge Base configuration:

  • Jira integration is disabled

You can enable these sources in your CodeRabbit configuration.

📥 Commits

Reviewing files that changed from the base of the PR and between fbe646d and 76c7377.

📒 Files selected for processing (1)
  • tests/manifest_jinja_tests.rs (3 hunks)
🧰 Additional context used
📓 Path-based instructions (1)
**/*.rs

📄 CodeRabbit Inference Engine (AGENTS.md)

**/*.rs: Comment why, not what. Explain assumptions, edge cases, trade-offs, or complexity. Don't echo the obvious.
Use functions and composition. Avoid repetition by extracting reusable logic. Prefer generators or comprehensions, and declarative code to imperative repetition when readable.
Small, meaningful functions. Functions must be small, clear in purpose, single responsibility, and obey command/query segregation.
Name things precisely. Use clear, descriptive variable and function names. For booleans, prefer names with is, has, or should.
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.
Place function attributes after doc comments.
Do not use return in single-line functions.
Prefer immutable data and avoid unnecessary mut bindings.
Handle errors with the Result type instead of panicking where feasible.
Prefer .expect() over .unwrap().
Use concat!() to combine long string literals rather than escaping newlines with a backslash.
Prefer single line versions of functions where appropriate.
Clippy warnings MUST be disallowed.
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.
Keep file size manageable. No single code file may be longer than 400 lines. Long switch statements or dispatch tables should be broken up by feature and constituents colocated with targets. Large blocks of test data should be moved to external data files.
Illustrate with clear examples. Function documentation must include clear examples demonstrating the usage and outcome of the function. Test documentation should omit examples where the example serves only to reiterate the test logic.
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.
...

Files:

  • tests/manifest_jinja_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.
  • Where code is only used by specific features, it must be conditionally compiled or a conditional expectation for unused_code applied.

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

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

  • Prefer mockall for mocks/stubs.

  • Prefer .expect() over .unwrap()

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

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

  • Files must not exceed 400 lines in length

    • Large modules must be decomposed
    • Long match statements or dispatch tables should be decomposed by domain and collocated with targets
    • Large blocks of inline data (e.g., test fixtures, constants or templates) must be moved to external files and inlined at compile-time or loaded at run-time.

Files:

  • tests/manifest_jinja_tests.rs
🧬 Code Graph Analysis (1)
tests/manifest_jinja_tests.rs (1)
src/manifest.rs (1)
  • from_str (24-45)
🔍 Remote MCP Deepwiki

Relevant findings to aid review (concise, factual):

  • Design/docs: The repo contains a detailed design describing a five-stage pipeline and template system; docs/netsuke-design.md and docs/roadmap.md document the ingestion pipeline, template functions (env(), glob()), security (shell-quote), and the roadmap item to remove the global first-pass Jinja parse. This is the authoritative place the PR updates to a YAML-first, three-stage (expand_foreach → deserialise → render strings) narrative should align with.,

  • Template engine & functions: The project uses minijinja with registered functions/filters (env, glob, basename, shell_escape, etc.); docs describe intended behavior and security (auto shell-escaping, explicit raw opt-out). Relevant when verifying that moving evaluation into value-level rendering preserves security/semantics.

  • Manifest parsing & API surface: manifest parsing and public API entry points (from_str, from_path) live in src/manifest.rs; docs and module layout show manifest.rs is the intended place for template/parse changes and that public API signatures are expected to remain stable. Confirm the PR kept from_str/from_path signatures.

  • Tests & test infra: The repo uses rstest, cucumber-rs (BDD), and insta snapshots. Tests and feature files live under tests/, with step definitions expecting target/source/dep assertions. The PR added/updated many tests (foreach.yml, render_target.yml, updated features and steps) — ensure step changes match step definitions and BDD expectations.

  • Examples: Examples under examples/ use foreach expressions and rely on template functions (glob, path ops). The PR replaced templated-string foreach values with bare expressions (e.g., "{{ glob(...) }}" → glob(...)); verify examples still conform to documented template function semantics and that eval timing (pre/post-deserialise) matches docs.

  • IR & security expectations: IR generation (src/ir.rs) enforces validation (RuleNotFound, DuplicateOutput, etc.) and action deduplication via stable hashing; rendering changes must not break these invariants or introduce nondeterminism in hashes.

Tools used:

  • read_wiki_structure (Deepwiki) — repository wiki pages listing and structure.
  • read_wiki_contents (Deepwiki) — full docs/pages content referenced above.
⏰ Context from checks skipped due to timeout of 120000ms. You can increase the timeout in your CodeRabbit configuration to a maximum of 15 minutes (900000ms). (1)
  • GitHub Check: build-test
🔇 Additional comments (6)
tests/manifest_jinja_tests.rs (6)

50-51: Quote the Jinja control block to preserve it as a YAML string.

Quoting the command fixes the YAML parse and matches value-level rendering semantics.


120-142: LGTM: single-item foreach expansion is correct.


210-222: LGTM: script recipe rendering is correct.


225-240: LGTM: rule recipe resolves with value-level rendering as expected.


243-246: LGTM: missing variable in target fields yields an error as intended.


252-258: LGTM: undefined in Jinja if produces a parse/render error under Strict undefined.

Comment thread tests/manifest_jinja_tests.rs Outdated
Comment thread tests/manifest_jinja_tests.rs Outdated
Comment thread tests/manifest_jinja_tests.rs Outdated
Comment thread tests/manifest_jinja_tests.rs
codescene-delta-analysis[bot]

This comment was marked as 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: 3

🔭 Outside diff range comments (3)
docs/netsuke-design.md (3)

548-550: Align wording with the new multi-stage pipeline

Replace “two-pass” with “multi-stage” to match the YAML-first, three-stage ingestion described in this section.

- The integration of a templating engine like Jinja fundamentally shapes the
- parsing pipeline, mandating a two-pass approach. It is impossible to parse the
+ The integration of a templating engine like Jinja fundamentally shapes the
+ parsing pipeline, mandating a multi-stage approach. It is not feasible to parse the
- user's `Netsukefile` file with `serde_yml` in a single step.
+ user's `Netsukefile` file with `serde_yml` in a single step.

1076-1084: Repair broken code fences in the Ninja snippet

The fenced block is malformed with a duplicate opening fence; markdownlint will fail and rendering will break.

 ```ninja
 # Generated from an ir::Action
 rule cc command = gcc -c -o $out $in description = CC $out depfile = $out.d
 deps = gcc
-
-```ninja
-

---

`1094-1111`: **Fix malformed Ninja and default snippets; close/open fences correctly**

The build edge example and the subsequent default statement have broken fences and stray text. Rewrite as two valid fenced blocks.

```diff
-  ```ninja
-  # Generated from an ir::BuildEdge
-  build foo.o: cc foo.c build bar.o: cc bar.c build my_app: link foo.o bar.o |
-
-  ```
-
-| lib_dependency.a |
-
-4\. **Write Defaults:** Finally, write the `default` statement, listing all
-paths from `graph.default_targets`.ninja
-
-default my_app
-
-``` 
+  ```ninja
+  # Generated from an ir::BuildEdge
+  build foo.o: cc foo.c
+  build bar.o: cc bar.c
+  build my_app: link foo.o bar.o | lib_dependency.a
+  ```
+
+4. **Write Defaults:** Finally, write the `default` statement, listing all
+paths from `graph.default_targets`.
+
+```ninja
+default my_app
+```
♻️ Duplicate comments (9)
docs/roadmap.md (1)

93-95: Grammar/style fix — LGTM

The completed item now conforms to the style guide and reads clearly.

tests/data/foreach.yml (1)

3-5: Use native YAML list for foreach data.

Drive the YAML-first contract by supplying a typed YAML list instead of a stringified Python list. This also avoids an unnecessary Jinja evaluation hop.

-  - foreach: "['foo', 'bar']"
+  - foreach:
+      - foo
+      - bar
tests/features/manifest.feature (1)

71-78: Assert index semantics explicitly (0-based) if exposed by the AST.

Lock down the 0-based index guarantee with explicit assertions. If step defs expose index, add steps like the following to prevent regressions.

   Scenario: Generating targets with foreach
     Given the manifest file "tests/data/foreach.yml" is parsed
     When the manifest is checked
     Then the manifest has 2 targets
     And the target 1 name is "0-foo"
     And the target 1 command is "echo 0 foo"
+    And the target 1 index is 0
     And the target 2 name is "1-bar"
     And the target 2 command is "echo 1 bar"
+    And the target 2 index is 1

If you want, I can add the step implementation to read index from the parsed target.

tests/steps/manifest_steps.rs (2)

147-153: Reduce repetition: use a StringOrList-to-string helper.

Avoid pattern-matching in every assertion that expects a single string. Extract a helper to validate and extract a single string for fields that are semantically singular.

 fn assert_target_name(world: &CliWorld, index: usize, name: &str) {
-    let target = get_target(world, index);
-    match &target.name {
-        StringOrList::String(value) => assert_eq!(value, name),
-        other => panic!("Expected StringOrList::String, got: {other:?}"),
-    }
+    let target = get_target(world, index);
+    let actual = get_string_from_string_or_list(&target.name, "name");
+    assert_eq!(&actual, name);
 }

Add this helper near assert_list_contains:

fn get_string_from_string_or_list(value: &StringOrList, field_name: &str) -> String {
    match value {
        StringOrList::String(s) => s.clone(),
        StringOrList::List(list) if list.len() == 1 => list[0].clone(),
        other => panic!("Expected String for {field_name}, got: {other:?}"),
    }
}

238-253: Unify rule-name extraction via the same helper.

Apply the same helper to the rule-name assertion to keep the tests DRY and error messages consistent.

 fn target_rule_is(world: &mut CliWorld, index: usize, rule_name: String) {
     let target = get_target(world, index);
     if let Recipe::Rule { rule } = &target.recipe {
-        match rule {
-            StringOrList::String(name) => assert_eq!(name, &rule_name),
-            other => panic!("Expected String, got: {other:?}"),
-        }
+        let actual = get_string_from_string_or_list(rule, "rule");
+        assert_eq!(actual, rule_name);
     } else {
         panic!("Expected rule recipe, got: {:?}", target.recipe);
     }
 }
tests/manifest_jinja_tests.rs (4)

66-92: Add a test for typed YAML lists in foreach.

Exercise the YAML-first flow by adding a test that uses a native YAML array (no quotes) for foreach. This locks down support beyond the stringified list form.

#[rstest]
fn expands_foreach_targets_typed_yaml_list() {
    let yaml = manifest_yaml(
        "targets:\n  - foreach: ['a', 'b']\n    name: '{{ item }}'\n    command: 'echo {{ item }}'\n",
    );

    let manifest = manifest::from_str(&yaml).expect("parse");
    let names: Vec<_> = manifest
        .targets
        .iter()
        .map(|t| match &t.name {
            netsuke::ast::StringOrList::String(s) => s.clone(),
            other => panic!("Expected String, got: {other:?}"),
        })
        .collect();
    assert_eq!(names, vec!["a", "b"]);
}

95-101: Fix boolean semantics in when-case (do not quote false).

Quoting false produces a non-empty string, which is truthy in Jinja/minijinja. This invalidates the intent of the test.

 #[rstest]
 #[case("[]", "", "no targets should be generated for empty foreach list")]
-#[case(
-    "['a', 'b']",
-    "'false'",
-    "no targets should be generated when condition is always false"
-)]
+#[case("['a', 'b']", "false", "no targets should be generated when condition is always false")]
 fn no_targets_generated_scenarios(

161-177: Also assert commands to fully validate per-item rendering.

Strengthen the test by asserting rendered commands alongside names.

     assert_eq!(names, vec!["a", "b"]);
+    let commands: Vec<_> = manifest
+        .targets
+        .iter()
+        .map(|t| match &t.recipe {
+            Recipe::Command { command } => command.clone(),
+            other => panic!("Expected command recipe, got: {other:?}"),
+        })
+        .collect();
+    assert_eq!(commands, vec!["echo a", "echo b"]);

179-215: Extract StringOrList assertions into helpers to remove duplication.

Replace repetitive matches with small helpers for equality and list-membership. This makes failures clearer and future changes cheaper.

Add helpers:

fn assert_string_or_list_eq(actual: &netsuke::ast::StringOrList, expected: &str, field: &str) {
    match actual {
        netsuke::ast::StringOrList::String(s) => assert_eq!(s, expected),
        netsuke::ast::StringOrList::List(list) if list.len() == 1 => assert_eq!(&list[0], expected),
        other => panic!("Expected String for {field}, got: {other:?}"),
    }
}

fn assert_string_or_list_contains(
    actual: &netsuke::ast::StringOrList,
    expected: &[String],
    field: &str,
) {
    match actual {
        netsuke::ast::StringOrList::List(list) => assert_eq!(list, expected),
        other => panic!("Expected List for {field}, got: {other:?}"),
    }
}

Then refactor the assertions in this test:

-    match &target.name {
-        netsuke::ast::StringOrList::String(n) => assert_eq!(n, "base1"),
-        other => panic!("Expected String, got: {other:?}"),
-    }
+    assert_string_or_list_eq(&target.name, "base1", "name");

-    match &target.sources {
-        netsuke::ast::StringOrList::List(list) => assert_eq!(list, &["base1.src".to_string()]),
-        other => panic!("Expected List, got: {other:?}"),
-    }
+    assert_string_or_list_contains(&target.sources, &["base1.src".to_string()], "sources");

-    match &target.deps {
-        netsuke::ast::StringOrList::List(list) => assert_eq!(list, &["base1.dep".to_string()]),
-        other => panic!("Expected List, got: {other:?}"),
-    }
+    assert_string_or_list_contains(&target.deps, &["base1.dep".to_string()], "deps");

-    match &target.order_only_deps {
-        netsuke::ast::StringOrList::List(list) => {
-            assert_eq!(list, &["base1.ord".to_string()]);
-        }
-        other => panic!("Expected List, got: {other:?}"),
-    }
+    assert_string_or_list_contains(
+        &target.order_only_deps,
+        &["base1.ord".to_string()],
+        "order_only_deps",
+    );
📜 Review details

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

💡 Knowledge Base configuration:

  • Jira integration is disabled

You can enable these sources in your CodeRabbit configuration.

📥 Commits

Reviewing files that changed from the base of the PR and between 76c7377 and a98b7c3.

📒 Files selected for processing (9)
  • docs/netsuke-design.md (9 hunks)
  • docs/roadmap.md (1 hunks)
  • examples/visual_design.yml (1 hunks)
  • examples/website.yml (1 hunks)
  • examples/writing.yml (1 hunks)
  • tests/data/foreach.yml (1 hunks)
  • tests/features/manifest.feature (1 hunks)
  • tests/manifest_jinja_tests.rs (3 hunks)
  • tests/steps/manifest_steps.rs (4 hunks)
🧰 Additional context used
📓 Path-based instructions (3)
**/*.md

📄 CodeRabbit Inference Engine (AGENTS.md)

**/*.md: Markdown files must use en-GB-oxendict ("-ize" / "-yse" / "-our") spelling and grammar. (EXCEPTION: the naming of the "LICENSE" file, which is to be left unchanged for community consistency.)
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 must be wrapped at 120 columns.
Tables and headings must not be wrapped.
Use dashes (-) for list bullets.
Use GitHub-flavoured Markdown footnotes ([^1]) for references and footnotes.

Files:

  • docs/roadmap.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
  • Headings must not be wrapped.
  • Documents must start with a level 1 heading
  • Headings must correctly increase or decrease by no more than one level at a time
  • Use GitHub-flavoured Markdown style for footnotes and endnotes.
  • Numbered footnotes must be numbered by order of appearance in the document.

Files:

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

📄 CodeRabbit Inference Engine (AGENTS.md)

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

Files:

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

📄 CodeRabbit Inference Engine (AGENTS.md)

**/*.rs: Comment why, not what. Explain assumptions, edge cases, trade-offs, or complexity. Don't echo the obvious.
Use functions and composition. Avoid repetition by extracting reusable logic. Prefer generators or comprehensions, and declarative code to imperative repetition when readable.
Small, meaningful functions. Functions must be small, clear in purpose, single responsibility, and obey command/query segregation.
Name things precisely. Use clear, descriptive variable and function names. For booleans, prefer names with is, has, or should.
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.
Place function attributes after doc comments.
Do not use return in single-line functions.
Prefer immutable data and avoid unnecessary mut bindings.
Handle errors with the Result type instead of panicking where feasible.
Prefer .expect() over .unwrap().
Use concat!() to combine long string literals rather than escaping newlines with a backslash.
Prefer single line versions of functions where appropriate.
Clippy warnings MUST be disallowed.
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.
Keep file size manageable. No single code file may be longer than 400 lines. Long switch statements or dispatch tables should be broken up by feature and constituents colocated with targets. Large blocks of test data should be moved to external data files.
Illustrate with clear examples. Function documentation must include clear examples demonstrating the usage and outcome of the function. Test documentation should omit examples where the example serves only to reiterate the test logic.
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.
...

Files:

  • tests/steps/manifest_steps.rs
  • tests/manifest_jinja_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.
  • Where code is only used by specific features, it must be conditionally compiled or a conditional expectation for unused_code applied.

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

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

  • Prefer mockall for mocks/stubs.

  • Prefer .expect() over .unwrap()

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

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

  • Files must not exceed 400 lines in length

    • Large modules must be decomposed
    • Long match statements or dispatch tables should be decomposed by domain and collocated with targets
    • Large blocks of inline data (e.g., test fixtures, constants or templates) must be moved to external files and inlined at compile-time or loaded at run-time.

Files:

  • tests/steps/manifest_steps.rs
  • tests/manifest_jinja_tests.rs
🧬 Code Graph Analysis (1)
tests/manifest_jinja_tests.rs (2)
src/manifest.rs (1)
  • from_str (24-45)
src/runner.rs (1)
  • is_empty (96-98)
🔍 Remote MCP Deepwiki

Relevant facts to aid reviewing PR #123 (concise):

  • Design & pipeline

    • Repo docs describe a five-stage pipeline; PR shifts to YAML-first, three-stage flow: parse YAML → expand foreach/when → render templated values in string fields. (docs explain same pipeline and required semantics)
  • Public AST / types

    • Docs show AST shapes and fields (NetsukeManifest, Rule, Target, Recipe, StringOrList). PR exposes concrete public AST structs matching the documented schema.
  • Manifest parsing (src/manifest.rs)

    • The manifest module is the intended place for template/parse changes; docs map manifest::from_str/from_path as public API entry points — PR keeps those signatures while changing internal flow to parse-first then foreach expansion and per-field rendering.
  • Template semantics & functions

    • Template engine (minijinja) functions (env, glob, basename, shell_escape, etc.) are documented as registered in the environment; docs emphasise security (automatic shell-escape) and that templating should be restricted to string values — the PR implements value-level rendering accordingly.
  • foreach/when behavior

    • Roadmap & docs specify foreach/when semantics (item/index iteration, per-iteration locals layered over target.vars) — PR implements foreach expansion phase (evaluate foreach, apply when filter, inject item/index vars) consistent with roadmap.
  • Examples & tests

    • Examples under examples/ were converted from moustache-quoted foreach strings to bare expressions (e.g., "{{ glob(...) }}" → glob(...)); docs show glob() returns lists used by templating and pipeline expectations. Tests updated/added (tests/data/*, features, steps, and unit tests) to validate foreach expansion, when filtering, rendering of command/script/rule fields, and error cases for non-iterable foreach. Check that step definitions and BDD scenarios align with updated step helpers and assertions.
  • IR & determinism concerns

    • Docs: IR (src/ir.rs) deduplicates actions via stable hashing and expects deterministic ordering; rendering changes must preserve deterministic outputs for snapshots/IR hashing. PR adds render_manifest then IR generation — verify ordering and hashing unchanged.
  • Error handling

    • Docs describe structured errors (thiserror/anyhow) and validation flow. PR added error constants (ERR_INITIAL_YAML_PARSE, ERR_MANIFEST_PARSE) and uses anyhow for contextual errors — ensure messages and propagation match project patterns.
  • Developer / CI expectations

    • Repo Makefile and docs require make fmt, lint, test, markdownlint, nixie; PR author ran these locally (nixie failed due to external mermaid-cli bun error). Ensure CI checks (format, lint, tests, snapshots) are green and that nixie failure is external, not code-related.

Tools/sources used:

  • Repository wiki / documentation fetch: read_wiki_contents, read_wiki_structure (Deepwiki),
⏰ Context from checks skipped due to timeout of 120000ms. You can increase the timeout in your CodeRabbit configuration to a maximum of 15 minutes (900000ms). (1)
  • GitHub Check: build-test
🔇 Additional comments (7)
examples/writing.yml (1)

19-24: Stabilise iteration order — LGTM

Switching to sorted(glob(...)) for foreach enforces deterministic target generation and resolves prior flakiness concerns.

examples/website.yml (1)

17-25: Use consistent | sort filter across examples

  • Replace | sorted with | sort in examples/website.yml to match other example files.
  • Update all other examples (e.g. examples/writing.yml Line 27) to use | sort for uniformity.
  • Verify that the templating environment registers a sort filter and not sorted.
-    sources: "{{ glob(site_dir ~ '/*.html') | exclude(site_dir ~ '/index.html') | sorted }}"
+    sources: "{{ glob(site_dir ~ '/*.html') | exclude(site_dir ~ '/index.html') | sort }}"
examples/visual_design.yml (1)

16-20: Deterministic iteration order — LGTM

Using sorted(glob(...)) for foreach locks in stable processing of SVGs and aligns with the YAML-first, value-level templating model.

docs/roadmap.md (1)

99-103: Document foreach/when semantics — LGTM

Marking foreach/when as implemented and documenting the item/index layering matches the code and tests added in this PR.

docs/netsuke-design.md (1)

596-600: Reaffirm the YAML-first separation — LGTM

The ingestion pipeline description matches the implementation: YAML parse → foreach/when expansion → post-deserialisation string rendering.

tests/steps/manifest_steps.rs (1)

37-43: Good: Centralise target lookup with get_target.

The helper removes repeated manifest/target plumbing and improves readability across step implementations.

tests/manifest_jinja_tests.rs (1)

50-50: LGTM: Quote the command Jinja block in YAML.

Quoting the Jinja conditional in the YAML string is correct for the value-level rendering phase.

Comment thread docs/netsuke-design.md Outdated
Comment thread docs/netsuke-design.md
Comment thread docs/netsuke-design.md Outdated
codescene-delta-analysis[bot]

This comment was marked as outdated.

Comment thread tests/manifest_jinja_tests.rs Outdated
codescene-delta-analysis[bot]

This comment was marked as outdated.

codescene-delta-analysis[bot]

This comment was marked as 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: 3

🔭 Outside diff range comments (2)
examples/writing.yml (1)

27-27: Fix undefined or inconsistent sorting filter.

Align with the rest of the examples and the documented function set; use sorted rather than sort to avoid a runtime template error.

-    sources: "{{ glob(build_dir ~ '/*.tex') | sort }}"
+    sources: "{{ glob(build_dir ~ '/*.tex') | sorted }}"
docs/netsuke-design.md (1)

561-565: Align wording with the YAML-first multi-stage approach

Do not refer to this as a “two-pass” approach in a section titled “YAML-First Multi-Stage Ingestion”.

Apply this fix:

-The integration of a templating engine like Jinja fundamentally shapes the
-parsing pipeline, mandating a two-pass approach. It is impossible to parse the
-user's `Netsukefile` file with `serde_yml` in a single step.
+The integration of a templating engine like Jinja fundamentally shapes the
+parsing pipeline, mandating a multi-stage approach. It is impractical to parse
+the user's `Netsukefile` into the final AST in a single step.
♻️ Duplicate comments (4)
examples/visual_design.yml (1)

16-16: Sort the globbed inputs to stabilise target generation order.

Ensure deterministic builds by sorting the foreach sequence.

-  - foreach: glob(src_dir ~ '/*.svg')
+  - foreach: sorted(glob(src_dir ~ '/*.svg'))
examples/website.yml (1)

17-17: Enforce deterministic build order by sorting the glob.

Stabilise target ordering across platforms and runs by sorting the foreach input.

-  - foreach: glob(pages_dir ~ '/*.md')
+  - foreach: sorted(glob(pages_dir ~ '/*.md'))
examples/writing.yml (1)

19-19: Sort the foreach sequence for deterministic output.

Stabilise chapter processing order to avoid flaky diffs and builds.

-  - foreach: glob(chapters_dir ~ '/*.md')
+  - foreach: sorted(glob(chapters_dir ~ '/*.md'))
tests/steps/manifest_steps.rs (1)

147-153: Extract string retrieval from StringOrList to a helper and reuse

Avoid repeating the pattern match on StringOrList across assertion functions.

Apply this refactor to use a shared helper:

 fn assert_target_name(world: &CliWorld, index: usize, name: &str) {
-    let target = get_target(world, index);
-    match &target.name {
-        StringOrList::String(value) => assert_eq!(value, name),
-        other => panic!("Expected StringOrList::String, got: {other:?}"),
-    }
+    let target = get_target(world, index);
+    let actual = get_string_from_string_or_list(&target.name, "name");
+    assert_eq!(actual, name);
 }

Add this helper near the other local helpers:

fn get_string_from_string_or_list<'a>(
    value: &'a StringOrList,
    field_name: &str,
) -> &'a str {
    match value {
        StringOrList::String(s) => s.as_str(),
        StringOrList::List(list) if list.len() == 1 => list[0].as_str(),
        other => panic!("Expected String for {field_name}, got: {other:?}"),
    }
}
📜 Review details

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

💡 Knowledge Base configuration:

  • Jira integration is disabled

You can enable these sources in your CodeRabbit configuration.

📥 Commits

Reviewing files that changed from the base of the PR and between a98b7c3 and 710fc35.

📒 Files selected for processing (10)
  • docs/netsuke-design.md (10 hunks)
  • examples/visual_design.yml (1 hunks)
  • examples/website.yml (1 hunks)
  • examples/writing.yml (1 hunks)
  • src/manifest.rs (1 hunks)
  • tests/data/foreach.yml (1 hunks)
  • tests/data/foreach_invalid.yml (1 hunks)
  • tests/features/manifest.feature (1 hunks)
  • tests/manifest_jinja_tests.rs (4 hunks)
  • tests/steps/manifest_steps.rs (4 hunks)
🧰 Additional context used
📓 Path-based instructions (5)
**/*.rs

📄 CodeRabbit Inference Engine (AGENTS.md)

**/*.rs: Comment why, not what; document assumptions, edge cases, trade-offs, and complexity without restating obvious code
Extract reusable logic into functions; prefer composition and declarative constructs when readable
Keep functions small, single-responsibility, and observe command/query separation
Use precise, descriptive names; boolean names should start with is/has/should
Use en-GB-oxendict spelling/grammar in comments (except external API references)
Function docs must include clear usage examples; avoid redundant examples in test docs
No Rust source file may exceed 400 lines; split long switches/tables; move large test data to external files
Fix test warnings in code; do not silence them
Extract helpers when functions are too long; group many parameters into well-named structs
Consider using Arc when returning large errors to reduce data movement
Document public APIs using Rustdoc (///) so cargo doc can generate documentation
Prefer immutable data; avoid unnecessary mut bindings
Prefer Result-based error handling over 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 helper functions when conditionals have more than two branches
Do not silence lints except as a last resort; suppressions must be tightly scoped and include a clear reason
Prefer #[expect(...)] over #[allow(...)] for lint management
Prefer .expect() over .unwrap()
Use conditional compilation (#[cfg]/#[cfg_attr]) for functions unused under specific feature sets
Use concat!() to join long string literals rather than using backslash-newline escapes
Prefer single-line function bodies where appropriate (e.g., pub fn new(id: u64) -> Self { Self(id) })
Prefer semantic error enums deriving std::error::Error via thiserror for caller-inspectable conditions

Files:

  • src/manifest.rs
  • tests/steps/manifest_steps.rs
  • tests/manifest_jinja_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.
  • Where code is only used by specific features, it must be conditionally compiled or a conditional expectation for unused_code applied.

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

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

  • Prefer mockall for mocks/stubs.

  • Prefer .expect() over .unwrap()

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

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

  • Files must not exceed 400 lines in length

    • Large modules must be decomposed
    • Long match statements or dispatch tables should be decomposed by domain and collocated with targets
    • Large blocks of inline data (e.g., test fixtures, constants or templates) must be moved to external files and inlined at compile-time or loaded at run-time.

Files:

  • src/manifest.rs
  • tests/steps/manifest_steps.rs
  • tests/manifest_jinja_tests.rs
src/**/*.rs

📄 CodeRabbit Inference Engine (AGENTS.md)

src/**/*.rs: Every module must begin with a //! module-level comment explaining its purpose and utility
Never export eyre::Report from library code; convert to domain error enums at API boundaries

Files:

  • src/manifest.rs
docs/**/*.md

📄 CodeRabbit Inference Engine (AGENTS.md)

docs/**/*.md: Treat docs/ markdown as the reference source of truth for requirements and decisions; keep updated with changes
Use en-GB-oxendict spelling/grammar in documentation (LICENSE name unchanged)

Files:

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

📄 CodeRabbit Inference Engine (AGENTS.md)

**/*.md: Validate Markdown with make markdownlint
Wrap Markdown paragraphs and bullet points at 80 columns; do not wrap tables or headings
Wrap code blocks in Markdown at 120 columns
Use dashes (-) for list bullets
Use GitHub-flavoured Markdown footnotes ([^1]) for references
Validate Mermaid diagrams in Markdown using make nixie

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
  • 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
tests/**/*.rs

📄 CodeRabbit Inference Engine (AGENTS.md)

tests/**/*.rs: Use rstest fixtures for shared setup and replace duplicated tests with #[rstest(...)] parameterised cases
Prefer mockall for mocks/stubs
Mock non-deterministic dependencies via dependency injection (e.g., Env/Clock traits) using mockable crate; follow docs/reliable-testing-in-rust-via-dependency-injection.md

Files:

  • tests/steps/manifest_steps.rs
  • tests/manifest_jinja_tests.rs
🧬 Code Graph Analysis (1)
tests/manifest_jinja_tests.rs (1)
src/manifest.rs (1)
  • from_str (24-45)
🔍 Remote MCP Deepwiki

Relevant additional facts for reviewing PR #123

  • Design & pipeline: repo docs describe shifting from the original five-stage flow to a YAML-first, three-stage ingestion: parse YAML → expand foreach/when → render string-valued templates; this is documented and aligned with the PR’s intent.,

  • Public AST: docs explicitly define the public AST shapes (NetsukeManifest, Rule, Target, Recipe, StringOrList) with serde attributes and deny_unknown_fields — matches PR’s added pub structs in src/ast.rs.

  • Manifest module responsibilities: manifest::from_str/from_path are the public entry points; manifest.rs is the expected location for parse/templating changes (rendering + foreach expansion belong here). PR keeps signatures but changes internal flow.

  • Foreach/when semantics: the roadmap/docs describe foreach/when as producing per-iteration targets with injected item/index vars layered over target.vars; glob() and env() functions are part of the templating function set. Tests and examples were updated to use bare expressions for foreach (no moustache wrapper).

  • Rendering & security: docs require templating be restricted to string values and mandate automatic shell-escaping (shell-quote + shlex validation) for commands — PR’s move to value-level rendering must preserve this escaping/validation.

  • IR determinism: IR generation deduplicates actions by stable hashing and requires deterministic ordering (snapshots rely on this). Rendering changes must not break ordering or hash stability used by ir::from_manifest and snapshot tests.

  • Error handling conventions: project uses thiserror for structured errors and anyhow for contextual propagation; manifest module in PR adds ERR_INITIAL_YAML_PARSE and ERR_MANIFEST_PARSE — ensure these follow existing patterns and provide comparable context/messages.

  • Tests & examples: tests were converted from Jinja for-loops to foreach-driven fixtures (new tests under tests/data/*, feature scenario updates, new step helpers). Examples changed foreach values from "{{ glob(...) }}" to glob(...). Review required: ensure tests cover non-iterable foreach errors, when-filtering, rendering of command/script/rule recipe types, and missing-var errors.

  • CI / developer tooling: Makefile targets (make fmt, check-fmt, lint, test, markdownlint, nixie) are expected; author ran them locally (nixie failed externally). Verify CI will run same checks and that any mermaid/nixie failure is external.

Tools/sources used

  • Repo wiki structure listing and page contents:,
⏰ Context from checks skipped due to timeout of 120000ms. You can increase the timeout in your CodeRabbit configuration to a maximum of 15 minutes (900000ms). (1)
  • GitHub Check: build-test
🔇 Additional comments (32)
tests/data/foreach_invalid.yml (1)

1-5: LGTM: Strong invalid-foreach fixture.

Use of a bare integer for foreach robustly exercises the “not iterable” error path.

tests/data/foreach.yml (1)

3-7: LGTM: Showcase YAML-first foreach with safe echoing.

Drive home the YAML-first design and avoid word-splitting by quoting the echoed item. This is a solid positive-case fixture.

tests/features/manifest.feature (2)

71-80: Lock in foreach generation and index semantics

The scenario asserts both names and index (0-based) and quotes the echoed item. This aligns with the YAML-first + foreach pipeline.


82-97: Exercise error paths and field rendering comprehensively

The added scenarios validate non-iterable foreach errors and per-field rendering (sources, deps, order-only, script, rule). This coverage is appropriate and aligned with the new AST semantics.

tests/steps/manifest_steps.rs (2)

37-43: Introduce a single source of truth to fetch targets

The get_target helper removes duplication and improves readability across steps. Keep it.


210-269: Additions expand behavioural coverage over all relevant fields

The new steps covering sources, deps, order-only deps, script, and rule variants look correct and reuse the get_target helper effectively.

src/manifest.rs (11)

3-6: Document the YAML-first contract at module entry

The module-level docs clearly state the separation: parse YAML, expand foreach/when, then render string values. Keep this as the source of truth.


25-45: Enforce strict undefineds and seed environment globals once

The flow (parse YAML → seed env globals from top-level vars → expand foreach → deserialise → render) matches the design and retains strict undefined behaviour to surface errors early.


47-64: Delegate foreach expansion cleanly and preserve order

The pass walks only targets and maintains deterministic ordering. This aligns with the YAML-first pipeline.


65-83: Keep foreach-less mappings intact; expand iterables with when/vars layering

The helper split (expand_target/when_allows/inject_iteration_vars) keeps complexity contained and readable.


85-96: Accept both native YAML sequences and string expressions in foreach

Handling typed YAML lists directly while evaluating string expressions via minijinja is correct and user-friendly.


97-111: Treat when as a proper expression over item/index

Using is_true() over a strict environment gives predictable filtering semantics.


147-160: Render actions, targets, and rules in a single traversal

The final rendering pass is cohesive and keeps per-entity concerns isolated. Good separation of concerns.


161-182: Render rule fields using env globals only

Rendering description, deps, and the recipe with an empty local context relies on env globals, which is correct for rules.


184-204: Render target fields with layered vars context

Rendering name/sources/deps/order-only and recipe against target.vars (on top of env globals) matches the documented precedence.


206-216: Snapshot target vars before rendering to avoid self-referential churn

Cloning vars up-front prevents cascading mutations. This is the right choice for determinism.


218-231: Render StringOrList variants in place

In-place rendering maintains structure and supports both single and list forms cleanly.

tests/manifest_jinja_tests.rs (10)

10-19: Centralise StringOrList assertions

The helpers reduce repetition and clarify intent across tests. Keep them.


71-71: Stabilise command templating with proper quoting in the string literal

Quoting the Jinja-if command string avoids YAML parsing ambiguity. Good fix.


87-113: Validate foreach expansion and per-item command rendering

Asserting both names and the quoted commands hardens the behaviour contract.


141-160: Assert single-item foreach expansion end-to-end

The test covers both target naming and command rendering for the single-item case. Looks good.


162-176: Parameterise non-iterable foreach error cases

Exercising both typed and string forms improves error-path coverage. Keep it.


178-195: Filter foreach items via when with item context

This test confirms item visibility and filtering semantics. Good coverage.


197-218: Render all core target fields with local vars precedence

The test robustly verifies name/sources/deps/order-only and command rendering with layered vars. Good.


220-249: De-duplicate recipe-type tests via rstest cases

Consolidating script and rule cases into a single parameterised test keeps the suite DRY without losing coverage. Good refactor.


251-268: Exercise error cases aligned with value-level rendering

Asserting errors on sources and inline if-blocks reflects the YAML-first, string-only rendering stage. Keep these.


115-139: Fix boolean semantics in when: do not quote false

Remove quotes around false so Jinja treats it as boolean false. No other quoted 'false' patterns remain in tests.

Apply this change:

-#[case(
-    "['a', 'b']",
-    "'false'",
-    "no targets should be generated when condition is always false"
-)]
+#[case("['a', 'b']", "false", "no targets should be generated when condition is always false")]
docs/netsuke-design.md (5)

261-263: Clarify enum variant selection by field presence

The updated sentence makes the selection criterion explicit. Keep it.


352-365: Document foreach/when expansion flow with a clear diagram

The mermaid diagram matches the implemented expansion semantics. Keep it.


401-409: Use en-GB spelling and accurate derive guidance

The text now uses “deserialise” and points to #[derive(Deserialize)] explicitly. Good editorial correction.


421-426: Reference concrete derive attributes for AST types

The derive guidance matches the public AST and is clear. Keep it.


610-614: Reinforce the three-stage ingestion within the docs

This paragraph correctly reiterates YAML-first parsing → expansion → final string rendering. Good alignment with code.

Comment thread docs/netsuke-design.md Outdated
Comment thread src/manifest.rs
Comment thread tests/steps/manifest_steps.rs
codescene-delta-analysis[bot]

This comment was marked as outdated.

Comment thread tests/manifest_jinja_tests.rs Outdated
codescene-delta-analysis[bot]

This comment was marked as outdated.

Comment thread tests/manifest_jinja_tests.rs
codescene-delta-analysis[bot]

This comment was marked as 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: 1

🔭 Outside diff range comments (1)
tests/manifest_jinja_tests.rs (1)

223-239: Also assert rendered commands to fully validate per-item rendering.

Strengthen the test by checking the recipe, not just names.

Apply this diff:

     let manifest = manifest::from_str(&yaml).expect("parse");
     assert_eq!(manifest.targets.len(), 2);
     let names: Vec<_> = manifest
         .targets
         .iter()
         .map(|t| match &t.name {
             netsuke::ast::StringOrList::String(s) => s.clone(),
             other => panic!("Expected String, got: {other:?}"),
         })
         .collect();
     assert_eq!(names, vec!["a", "b"]);
+
+    let commands: Vec<_> = manifest
+        .targets
+        .iter()
+        .map(|t| match &t.recipe {
+            Recipe::Command { command } => command.clone(),
+            other => panic!("Expected command recipe, got: {other:?}"),
+        })
+        .collect();
+    assert_eq!(commands, vec!["echo 'a'", "echo 'b'"]);
♻️ Duplicate comments (1)
tests/manifest_jinja_tests.rs (1)

159-166: Fix boolean semantics in when-case: do not quote false.

Quoting false produces a non-empty string which is truthy in Jinja/minijinja, defeating the test intent.

Apply this diff:

 #[rstest]
 #[case("[]", "", "no targets should be generated for empty foreach list")]
 #[case(
     "['a', 'b']",
-    "'false'",
+    "false",
     "no targets should be generated when condition is always false"
 )]
📜 Review details

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

💡 Knowledge Base configuration:

  • Jira integration is disabled

You can enable these sources in your CodeRabbit configuration.

📥 Commits

Reviewing files that changed from the base of the PR and between 710fc35 and 5d06626.

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

📄 CodeRabbit Inference Engine (AGENTS.md)

**/*.rs: Comment why, not what; document assumptions, edge cases, trade-offs, and complexity without restating obvious code
Extract reusable logic into functions; prefer composition and declarative constructs when readable
Keep functions small, single-responsibility, and observe command/query separation
Use precise, descriptive names; boolean names should start with is/has/should
Use en-GB-oxendict spelling/grammar in comments (except external API references)
Function docs must include clear usage examples; avoid redundant examples in test docs
No Rust source file may exceed 400 lines; split long switches/tables; move large test data to external files
Fix test warnings in code; do not silence them
Extract helpers when functions are too long; group many parameters into well-named structs
Consider using Arc when returning large errors to reduce data movement
Document public APIs using Rustdoc (///) so cargo doc can generate documentation
Prefer immutable data; avoid unnecessary mut bindings
Prefer Result-based error handling over 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 helper functions when conditionals have more than two branches
Do not silence lints except as a last resort; suppressions must be tightly scoped and include a clear reason
Prefer #[expect(...)] over #[allow(...)] for lint management
Prefer .expect() over .unwrap()
Use conditional compilation (#[cfg]/#[cfg_attr]) for functions unused under specific feature sets
Use concat!() to join long string literals rather than using backslash-newline escapes
Prefer single-line function bodies where appropriate (e.g., pub fn new(id: u64) -> Self { Self(id) })
Prefer semantic error enums deriving std::error::Error via thiserror for caller-inspectable conditions

Files:

  • tests/manifest_jinja_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.
  • Where code is only used by specific features, it must be conditionally compiled or a conditional expectation for unused_code applied.

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

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

  • Prefer mockall for mocks/stubs.

  • Prefer .expect() over .unwrap()

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

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

  • Files must not exceed 400 lines in length

    • Large modules must be decomposed
    • Long match statements or dispatch tables should be decomposed by domain and collocated with targets
    • Large blocks of inline data (e.g., test fixtures, constants or templates) must be moved to external files and inlined at compile-time or loaded at run-time.

Files:

  • tests/manifest_jinja_tests.rs
tests/**/*.rs

📄 CodeRabbit Inference Engine (AGENTS.md)

tests/**/*.rs: Use rstest fixtures for shared setup and replace duplicated tests with #[rstest(...)] parameterised cases
Prefer mockall for mocks/stubs
Mock non-deterministic dependencies via dependency injection (e.g., Env/Clock traits) using mockable crate; follow docs/reliable-testing-in-rust-via-dependency-injection.md

Files:

  • tests/manifest_jinja_tests.rs
🧬 Code Graph Analysis (1)
tests/manifest_jinja_tests.rs (1)
src/manifest.rs (1)
  • from_str (24-45)
🔍 Remote MCP Deepwiki

Summary of additional relevant facts for reviewing PR #123

  • Project documentation already describes and prescribes the YAML-first, multi-stage pipeline (parse YAML → template expansion/foreach → deserialize → render string fields → IR → Ninja); the PR aligns with that design and exposes the public AST (NetsukeManifest, Rule, Target, Recipe, StringOrList) with serde deny_unknown_fields semantics.

  • Module mapping / responsibilities:

    • src/manifest.rs is the canonical place for manifest ingestion/rendering; from_str/from_path are public entry points (signatures unchanged). The manifest module is documented to perform templating (minijinja) before serde deserialisation in the intended pipeline; PR changes manifest.rs to parse YAML first, run foreach expansion, then deserialize and render string fields — consistent with docs.
  • Foreach/when semantics and template functions:

    • Roadmap/docs specify foreach/when should generate per-iteration targets with injected item/index and restrict templating to string values; built-in functions include glob() and env(). Examples in tests and examples were updated to use bare expressions for foreach (e.g., foreach: glob(...)). PR changes to examples/tests follow this.
  • Tests updated/added:

    • BDD scenarios (tests/features/*.feature), cucumber step helpers, and unit tests were converted from Jinja loop fixtures to foreach-driven fixtures; new test data files added (tests/data/foreach.yml, foreach_invalid.yml, render_target.yml) and old Jinja-for tests removed. Tests cover: foreach expansion, when filtering, non-iterable foreach errors, rendering of name/sources/deps/order-only-deps/command/script/rule, and missing-var errors.
  • Security and determinism concerns to verify in review:

    • Docs mandate automatic shell-escaping (shell-quote) and shlex validation for commands and require IR determinism (stable ordering, SHA-based action deduplication). The PR introduces a new render phase and foreach expansion that must preserve escaping/validation and not alter ordering/hash stability used by ir::from_manifest and snapshot tests. Confirm that render_manifest + render helpers apply shell-escape + shlex validation where required and that action hashing remains stable.
  • Error handling conventions:

    • Project uses thiserror for structured errors and anyhow for contexts. PR adds ERR_INITIAL_YAML_PARSE and ERR_MANIFEST_PARSE constants — verify they follow existing error messaging/context conventions.
  • CI / tooling:

    • Makefile targets (make fmt, check-fmt, lint, test, markdownlint, nixie) are standard; author ran them locally (nixie failed due to external mermaid-cli bun error). Ensure CI runs the same gates and note nixie/mermaid is an external dependency if doc changes include mermaid diagrams.

Files/sections to focus review on (most relevant):

  • src/manifest.rs (foreach expansion, parse→expand→deserialize→render flow, error contexts)
  • src/ast.rs (new pub structs/enums, serde attrs)
  • tests/** (new/updated feature scenarios and data files)
  • examples/*.yml (foreach expression changes)
  • ir module / hashing logic (ensure no regression in determinism)
  • places where shell-quote / shlex validation are invoked after the new render phase

Sources used: repository wiki pages and docs (Overview / Architecture / Manifest Parsing / Roadmap / Testing) via read_wiki_structure and read_wiki_contents.,

⏰ Context from checks skipped due to timeout of 120000ms. You can increase the timeout in your CodeRabbit configuration to a maximum of 15 minutes (900000ms). (1)
  • GitHub Check: build-test
🔇 Additional comments (14)
tests/manifest_jinja_tests.rs (14)

1-2: Document the three-stage pipeline upfront (LGTM).

Keep this succinct module doc; it aligns with the YAML-first pipeline and satisfies the module-level doc requirement.


11-19: Extract assertion helper for StringOrList (LGTM).

Improve test readability and failure messages without duplicating pattern matches.


21-30: Provide list assertion helper (LGTM).

Lock down field shape and ordering, which also reinforces determinism expectations.


32-37: Deduplicate target extraction with a generic helper (LGTM).

Eliminate structural duplication flagged by CodeScene by composing over a closure.


39-44: Name extraction uses precise shape checks (LGTM).

Panic early on unexpected shapes to surface regressions fast in tests.


46-51: Command extraction is scoped to the Command recipe (LGTM).

Keep this strict; add more extractors if future tests cover Script/Rule extraction.


93-93: Quote the templated command string (LGTM).

Quote to avoid YAML parsing ambiguities and ensure the Jinja block is treated as a single scalar.


108-131: Parameterise foreach tests to cover typed and string list inputs (LGTM).

Exercise both native YAML arrays and stringified lists, and assert both item and index use.


131-157: Assert names and commands with shared extractors (LGTM).

Keep transformations local and assertions clear; this locks down stable ordering.


185-204: Single-item foreach path (LGTM).

Assert exact cardinality and the rendered command; this is a useful guard for off-by-one bugs.


206-220: Non-iterable foreach error coverage (LGTM).

Exercise both typed and string forms; this closes a common misconfiguration gap.


241-262: Per-target field rendering under foreach (LGTM).

Validate name/sources/deps/order-only-deps and the shell-quoted command; this is aligned with the string-only rendering rule.


265-293: Unify recipe-type rendering checks (LGTM).

Parameterise Script and Rule cases to remove duplication, preserving strict matching and clear panics on mismatched recipe types.


295-312: Error-path coverage for missing vars and undefined in if (LGTM).

Assert strict undefined behaviour post-deserialisation for both list elements and inline conditionals.

Comment thread tests/manifest_jinja_tests.rs
codescene-delta-analysis[bot]

This comment was marked as 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: 5

🔭 Outside diff range comments (1)
tests/manifest_jinja_tests.rs (1)

243-258: Also assert rendered commands to fully validate filtering.

Validate both names and commands after when-based filtering to exercise per-item rendering end-to-end.

     let names: Vec<_> = manifest
         .targets
         .iter()
         .map(|t| match &t.name {
             netsuke::ast::StringOrList::String(s) => s.clone(),
             other => panic!("Expected String, got: {other:?}"),
         })
         .collect();
     assert_eq!(names, vec!["a", "b"]);
+
+    let commands: Vec<_> = manifest
+        .targets
+        .iter()
+        .map(|t| match &t.recipe {
+            Recipe::Command { command } => command.clone(),
+            other => panic!("Expected command recipe, got: {other:?}"),
+        })
+        .collect();
+    assert_eq!(commands, vec!["echo 'a'", "echo 'b'"]);
♻️ Duplicate comments (3)
tests/steps/manifest_steps.rs (3)

10-10: Resolve previous nit: use a named constant for injected index key — good.

Eliminate magic strings and reduce typo risk when accessing vars["index"].


157-161: Deduplicate StringOrList extraction — good refactor.

Use the shared extractor to keep assertions consistent and terse.


163-170: Target retrieval reuse — good.

Route all field assertions through get_target to remove repeated manifest/target lookups.

📜 Review details

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

💡 Knowledge Base configuration:

  • Jira integration is disabled

You can enable these sources in your CodeRabbit configuration.

📥 Commits

Reviewing files that changed from the base of the PR and between 5d06626 and 8faff59.

📒 Files selected for processing (2)
  • tests/manifest_jinja_tests.rs (5 hunks)
  • tests/steps/manifest_steps.rs (4 hunks)
🧰 Additional context used
📓 Path-based instructions (2)
**/*.rs

📄 CodeRabbit Inference Engine (AGENTS.md)

**/*.rs: Comment why, not what; document assumptions, edge cases, trade-offs, and complexity without restating obvious code
Extract reusable logic into functions; prefer composition and declarative constructs when readable
Keep functions small, single-responsibility, and observe command/query separation
Use precise, descriptive names; boolean names should start with is/has/should
Use en-GB-oxendict spelling/grammar in comments (except external API references)
Function docs must include clear usage examples; avoid redundant examples in test docs
No Rust source file may exceed 400 lines; split long switches/tables; move large test data to external files
Fix test warnings in code; do not silence them
Extract helpers when functions are too long; group many parameters into well-named structs
Consider using Arc when returning large errors to reduce data movement
Document public APIs using Rustdoc (///) so cargo doc can generate documentation
Prefer immutable data; avoid unnecessary mut bindings
Prefer Result-based error handling over 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 helper functions when conditionals have more than two branches
Do not silence lints except as a last resort; suppressions must be tightly scoped and include a clear reason
Prefer #[expect(...)] over #[allow(...)] for lint management
Prefer .expect() over .unwrap()
Use conditional compilation (#[cfg]/#[cfg_attr]) for functions unused under specific feature sets
Use concat!() to join long string literals rather than using backslash-newline escapes
Prefer single-line function bodies where appropriate (e.g., pub fn new(id: u64) -> Self { Self(id) })
Prefer semantic error enums deriving std::error::Error via thiserror for caller-inspectable conditions

Files:

  • tests/steps/manifest_steps.rs
  • tests/manifest_jinja_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.
  • Where code is only used by specific features, it must be conditionally compiled or a conditional expectation for unused_code applied.

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

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

  • Prefer mockall for mocks/stubs.

  • Prefer .expect() over .unwrap()

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

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

  • Files must not exceed 400 lines in length

    • Large modules must be decomposed
    • Long match statements or dispatch tables should be decomposed by domain and collocated with targets
    • Large blocks of inline data (e.g., test fixtures, constants or templates) must be moved to external files and inlined at compile-time or loaded at run-time.

Files:

  • tests/steps/manifest_steps.rs
  • tests/manifest_jinja_tests.rs
tests/**/*.rs

📄 CodeRabbit Inference Engine (AGENTS.md)

tests/**/*.rs: Use rstest fixtures for shared setup and replace duplicated tests with #[rstest(...)] parameterised cases
Prefer mockall for mocks/stubs
Mock non-deterministic dependencies via dependency injection (e.g., Env/Clock traits) using mockable crate; follow docs/reliable-testing-in-rust-via-dependency-injection.md

Files:

  • tests/steps/manifest_steps.rs
  • tests/manifest_jinja_tests.rs
🧬 Code Graph Analysis (1)
tests/manifest_jinja_tests.rs (1)
src/manifest.rs (1)
  • from_str (24-45)
🔍 Remote MCP Deepwiki

Relevant facts found for reviewing PR #123

  • Repo docs already specify the YAML-first multi-stage pipeline (parse YAML → template expansion/foreach → deserialize → render string fields → IR → Ninja). The PR’s changes to src/manifest.rs (parse YAML first, run foreach expansion, then render string fields) align with that design intent.,

  • The AST surface is public and strict (NetsukeManifest, Rule, Target, Recipe, StringOrList) with serde deny_unknown_fields; deserialisation is expected to be strict and is a key contract for manifest parsing. Changes to src/ast.rs (new pub structs/enums) must preserve serde behavior.

  • Manifest parsing responsibilities: manifest::from_str/from_path are the public entry points; manifest.rs now does: parse YAML → load vars into minijinja env → expand foreach/when → serde_yml::from_value → render string fields. Pay attention to new error constants ERR_INITIAL_YAML_PARSE and ERR_MANIFEST_PARSE and use of anyhow for context.

  • Foreach/when semantics: foreach and when are intended to generate per-iteration targets with injected item/index vars; docs and roadmap mark foreach/when as implemented and examples/tests updated to use bare expressions (e.g., foreach: glob(...)). Ensure expand_foreach and inject_iteration_vars implement item/index scoping and layering over target.vars/manifest.vars as described.

  • Tests and examples: many BDD feature files, test data, and step helpers were added/updated to exercise foreach expansion, when filtering, non-iterable foreach errors, rendering of all target fields (sources/deps/order-only-deps/command/script/rule), and per-target index injection. Validate test expectations match implementation (string rendering only in string fields; foreach evaluated prior to deserialisation).

  • Security / determinism concerns called out in docs that must be preserved by the new flow:

    • Automatic shell-escaping (shell-quote) and shlex validation of final commands.
    • IR determinism (stable ordering, SHA-based action deduplication) — verify render_manifest and foreach expansion do not break ordering or hashing used by ir::from_manifest and snapshot tests.
  • CI/tooling note: Makefile targets include make nixie to validate mermaid diagrams; author ran nixie locally and it failed due to external mermaid-cli bun error. If docs added/changed include mermaid diagrams (PR does), CI may fail or require the external tool.

Tools/sources used: functions.read_wiki_structure, functions.read_wiki_contents (wiki content exported above).,

⏰ Context from checks skipped due to timeout of 120000ms. You can increase the timeout in your CodeRabbit configuration to a maximum of 15 minutes (900000ms). (1)
  • GitHub Check: build-test
🔇 Additional comments (6)
tests/steps/manifest_steps.rs (3)

172-181: Index assertion helper reads cleanly and is type-safe.

Using INDEX_KEY and usize conversion is clear and robust.


218-247: Add list-field steps using the shared helpers — good coverage with low duplication.

The steps for sources/dep/order-only dep lean on get_target and assert_list_contains appropriately.


248-275: Recipe-specific step defs are precise.

Keep the panic messages recipe-specific to ease failures triage.

tests/manifest_jinja_tests.rs (3)

1-2: Document the YAML-first pipeline up-front — good orientation.

The module-level docs succinctly anchor the new parse → foreach/when → render flow.


93-93: Quote the Jinja command string correctly in YAML — good fix.

Use of double-quotes prevents YAML from mangling braces and matches the new “render string values” phase.


283-312: Parameterise recipe-type assertions — good deduplication.

Collapse script/rule variants into one test while keeping variant-specific checks readable.

Comment thread tests/manifest_jinja_tests.rs
Comment thread tests/manifest_jinja_tests.rs
Comment thread tests/manifest_jinja_tests.rs
Comment thread tests/steps/manifest_steps.rs
Comment thread tests/steps/manifest_steps.rs
codescene-delta-analysis[bot]

This comment was marked as outdated.

Reject targets with a non-mapping `vars` field so configuration errors are surfaced.\n\nAlso clarify when Jinja rendering occurs in the design doc and add a regression test.
codescene-delta-analysis[bot]

This comment was marked as outdated.

codescene-delta-analysis[bot]

This comment was marked as outdated.

codescene-delta-analysis[bot]

This comment was marked as outdated.

codescene-delta-analysis[bot]

This comment was marked as outdated.

@leynos leynos merged commit 094d332 into main Aug 18, 2025
4 checks passed
@leynos leynos deleted the codex/implement-dynamic-features-for-custom-functions branch August 18, 2025 00:21
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