Skip to content

Lower target and action deps (3.14.3)#315

Open
leynos wants to merge 12 commits into
mainfrom
3-14-3-lower-target-and-action-deps
Open

Lower target and action deps (3.14.3)#315
leynos wants to merge 12 commits into
mainfrom
3-14-3-lower-target-and-action-deps

Conversation

@leynos
Copy link
Copy Markdown
Owner

@leynos leynos commented May 23, 2026

Summary

This branch implements roadmap task (3.14.3) by lowering target and action deps into a dedicated implicit-dependency class in the IR and generated Ninja output. It keeps sources as the explicit recipe-input class used by $in and {{ ins }}, while deps now affect dependency ordering, rebuild decisions, and cycle detection without appearing in recipe arguments.

Roadmap task: (3.14.3)
Execplan: docs/execplans/3-14-3-lower-target-and-action-deps.md

Review walkthrough

Validation

  • cargo test --workspace: passed after the IR field addition.
  • cargo test --test ir_from_manifest_tests: passed with 15 tests after manifest lowering.
  • cargo test -p netsuke ir::cycle: passed after implicit-dependency cycle traversal.
  • cargo test --test ninja_gen_tests: passed after Ninja emission updates.
  • cargo test --test bdd_tests implicit: passed after behavioural coverage was added.
  • make check-fmt: passed in the final gate.
  • make lint: passed in the final gate.
  • make test: passed in the final gate.
  • make markdownlint: passed in the final gate.
  • make nixie: passed in the final gate.
  • coderabbit review --agent: completed with zero findings.

Notes

make fmt still triggers a pre-existing repository-wide Markdown formatting backlog and rewrites unrelated documentation before failing. This branch restored that unrelated churn and used the non-mutating gates above as the applicable completion checks.

No new external crate dependencies were added. The planned proptest coverage was replaced with deterministic bounded cycle coverage because proptest is not currently in the dependency graph and the execplan requires explicit approval before adding dependencies.

Summary by Sourcery

Lower manifest target and action deps into a dedicated implicit-dependency class in the IR and generated Ninja output, aligning cycle detection, recipe interpolation, tests, and documentation with the intended dependency semantics.

New Features:

  • Introduce implicit_deps on IR build edges to represent manifest deps separately from explicit sources inputs.
  • Support manifest deps for both targets and actions as implicit dependencies that appear in generated Ninja files using Ninja's | syntax.

Enhancements:

  • Update cycle detection to traverse both explicit inputs and implicit dependencies while keeping order-only dependencies excluded.
  • Preserve {{ ins }} and {{ outs }} placeholders through manifest rendering so recipe interpolation can expand only explicit sources and outputs.
  • Clarify and expand user, developer, design, and formal-verification documentation around dependency classes and cycle participation, and mark roadmap item 3.14.3 as complete.

Tests:

  • Add IR unit tests to ensure manifest deps populate implicit dependencies and do not affect explicit recipe inputs.
  • Extend cycle-detection tests to cover implicit-only, mixed, missing-implicit, and bounded small cycles.
  • Add Ninja generation tests and BDD scenarios validating implicit dependency lowering, Ninja edge formatting, and recipe interpolation behaviour.

Draft an execution plan for lowering target and action `deps` into a new
implicit-dependency class on the IR `BuildEdge` and emitting them as
Ninja `|` implicit dependencies between explicit inputs and `||`
order-only deps.

The plan records the cycle-participation decision (explicit inputs and
implicit deps participate; order-only deps do not), defers rule-level
`Rule.deps` rejection to roadmap item 3.14.6, and keeps action hashing
unchanged because `implicit_deps` lives on `BuildEdge` rather than
`Action`. Validation runs through `make check-fmt`, `make lint`,
`make test`, `make markdownlint`, `make nixie`, and `coderabbit review
--agent`.

The plan is DRAFT and must be approved before implementation begins.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
@coderabbitai
Copy link
Copy Markdown
Contributor

coderabbitai Bot commented May 23, 2026

Review Change Stack

Overview

Implements roadmap task 3.14.3: Lower target and action deps into implicit IR and Ninja edges. This change separates manifest deps (implicit dependencies) from sources (explicit recipe inputs), allowing dependencies to trigger rebuilds and cycle detection without appearing in recipe arguments ($in / {{ ins }}).

Key Changes

Core IR Implementation

  • Added implicit_deps: Vec<Utf8PathBuf> field to BuildEdge in src/ir/graph.rs
  • Updated BuildGraph::process_targets in src/ir/from_manifest.rs to populate implicit_deps from manifest Target.deps
  • Extended cycle detection in src/ir/cycle.rs to traverse both inputs and implicit_deps when searching for cycles (excluding order_only_deps)

Ninja Output

  • Updated src/ninja_gen.rs to emit implicit dependencies using Ninja's | <paths> syntax, positioned after explicit inputs and before order-only dependencies

Recipe Handling

  • Modified src/ir/cmd_interpolate.rs to preserve {{ ins }} and {{ outs }} template placeholders during substitution, preprocessing them before the existing backtick-aware $in/$out pass
  • Enhanced src/manifest/render.rs to ensure ins and outs variables exist in template contexts by inserting default manifest values ("{{ ins }}" / "{{ outs }}") when not already present

Testing & Refactoring

Cycle Detection Tests

  • Refactored duplicate test logic in src/ir/cycle.rs by extracting a shared helper function assert_missing_deps that constructs a CycleDetector, invokes visit(path("a")), and validates missing dependencies
  • The helper signature was adjusted to accept &HashMap by reference rather than by value to satisfy Clippy's needless_pass_by_value lint
  • Extended test coverage to include self-edges, implicit-only cycles, and mixed explicit/implicit cycle detection

New Test Coverage

  • Added tests/data/implicit_deps.yml fixture to support feature tests
  • tests/ir_from_manifest_tests.rs: New #[rstest] cases validate that manifest deps populate edge.implicit_deps (not edge.inputs) and that deps do not contribute to recipe command interpolation
  • tests/ninja_gen_tests.rs: Extended snapshot and parametrised test cases covering implicit dependencies alone, combined with explicit inputs, and across all three dependency classes
  • tests/features/ninja.feature: Added BDD scenario asserting that generated Ninja content correctly reflects implicit dependencies in build lines
  • tests/bdd/steps/ir.rs: Introduced parse_path_list, TargetPathField enum, and assert_target_paths helper to validate IR graph target edge path lists for both inputs and implicit_deps

Documentation

  • Updated docs/formal-verification-methods-in-netsuke.md to clarify cycle-participation contract: detector traverses explicit inputs and implicit_deps, excluding order_only_deps
  • Enhanced docs/netsuke-design.md with refined inline documentation of BuildEdge.inputs and .implicit_deps fields
  • Updated docs/users-guide.md "Defining Targets and Actions" section to clarify that sources are explicit recipe inputs and deps are implicit dependencies triggering rebuilds but excluded from $in/{{ ins }}
  • Added docs/developers-guide.md section "IR dependency classes" documenting the lowering of manifest fields to IR BuildEdge fields
  • Updated docs/roadmap.md to mark task 3.14.3 and all subtasks as completed
  • Added new execplan document docs/execplans/3-14-3-lower-target-and-action-deps.md detailing the implementation approach, validation plan, and retrospective on completion

Validation

All existing workspace tests, targeted test suites, linting, and formatting gates passed. No new external crates were added.

Walkthrough

This PR implements the complete lowering of manifest deps into implicit IR dependencies and Ninja edges. The feature adds an implicit_deps field to BuildEdge, extends cycle detection to traverse these dependencies, updates Ninja output formatting, and ensures command template variables are properly handled. Comprehensive test coverage and documentation changes complete the roadmap item 3.14.3.

Changes

Implicit dependencies implementation and lowering

Layer / File(s) Summary
IR data structure for implicit dependencies
src/ir/graph.rs, docs/netsuke-design.md
BuildEdge gains a public implicit_deps: Vec<Utf8PathBuf> field; design documentation clarifies the field maps to Ninja | syntax and remains distinct from recipe inputs ($in / {{ ins }}).
Populate implicit dependencies from manifest
src/ir/from_manifest.rs
Target deps fields are converted to Utf8PathBuf and populated into BuildEdge.implicit_deps during IR graph construction.
Cycle detection traversal and tests
src/ir/cycle.rs
CycleDetector::visit now traverses both edge.inputs and edge.implicit_deps to detect cycles reachable through either path; test infrastructure extended to construct edges with implicit dependencies; comprehensive coverage added for self-edges, implicit-only cycles, mixed explicit/implicit cycles, and bounded cycle detection across multiple lengths.
Ninja generator output for implicit dependencies
src/ninja_gen.rs
DisplayEdge conditionally appends | <paths> segment for non-empty implicit_deps following inputs and preceding order-only deps; documentation examples and test setup updated.
Command template variable handling
src/manifest/render.rs, src/ir/cmd_interpolate.rs
Recipe rendering introduces render_recipe_str_with helper ensuring ins and outs variables always exist in template context; command interpolation preprocesses {{ ins }} and {{ outs }} placeholders before Ninja substitution.
BDD and integration test coverage
tests/data/implicit_deps.yml, tests/features/ninja.feature, tests/bdd/steps/ir.rs, tests/ir_from_manifest_tests.rs
Test fixture defines targets and actions with both sources and deps; feature scenario validates IR captures implicit dependencies and Ninja output renders them correctly; BDD step definitions assert target edge paths; manifest-to-IR tests verify deps become implicit_deps and do not contribute to recipe inputs.
Unit test updates
tests/ir_tests.rs, tests/ninja_gen_tests.rs, tests/bdd_tests.rs
Existing tests updated to initialise implicit_deps: Vec::new(); new Ninja generation test cases cover combinations of explicit inputs, implicit dependencies, and order-only dependencies; feature comment punctuation corrected.
Complete documentation and roadmap update
docs/developers-guide.md, docs/formal-verification-methods-in-netsuke.md, docs/users-guide.md, docs/execplans/3-14-3-lower-target-and-action-deps.md, docs/roadmap.md, docs/netsuke-design.md
Developers guide explains IR dependency lowering; cycle participation contract clarifies traversal includes implicit deps but excludes order-only deps; user guide specifies deps map to Ninja | and do not appear in recipe inputs; ExecPlan 3.14.3 documents complete implementation stages, decision log, and validation procedures; roadmap marks task 3.14.3 and all subtasks complete.

Suggested labels

Roadmap

Poem

A graph that builds, now implicit and true,
Dependencies lurk where the cycle-check flew,
From manifest down to the Ninja's |,
Deps trigger rebuilds without {{ ins }} to lie,
The blueprint complete—the roadmap ascends! 📋✨

🚥 Pre-merge checks | ✅ 15 | ❌ 5

❌ Failed checks (5 warnings)

Check name Status Explanation Resolution
User-Facing Documentation ⚠️ Warning README.md describes deps as "explicit dependencies" but users-guide now documents them as implicit (not in {{ ins }}). This breaking change requires README updates. Update README.md Targets section to clarify deps are implicit dependencies excluded from recipe arguments, matching users-guide documentation.
Developer Documentation ⚠️ Warning Architectural changes are well documented in developers guide and design docs. However, two review-suggested inline documentation comments were not added to the code. Add Stage 1/Stage 2 comment in cmd_interpolate.rs substitute() explaining preprocessing. Add /// doc comment to render_recipe_str_with() in manifest/render.rs explaining ins/outs placeholder injection.
Testing (Property / Proof) ⚠️ Warning PR introduces cycle-detection invariant requiring proptest per ExecPlan; implementation chose bounded deterministic tests instead without documented escalation approval. Add proptest coverage (ExecPlan Stage D), or document escalation approval explicitly deferring cycle-proof validation to roadmap item 4.2.1.
Testing (Compile-Time / Ui) ⚠️ Warning PR lacks snapshot tests for implicit_deps Ninja output (text-based, structured, UI-adjacent output). Uses only hardcoded parametrised assertions instead of insta snapshots. Add snapshot test using assert_snapshot!() for Ninja generation with implicit_deps scenarios to ensure text output stability and reviewability.
Architectural Complexity And Maintainability ⚠️ Warning Missing documented conventions. render_recipe_str_with and substitute lack required doc/inline comments explaining their behaviour, creating undocumented conventions maintainers must discover. Add doc comment to render_recipe_str_with explaining default ins/outs placeholder injection and inline comment to substitute explaining two-stage preprocessing.
✅ Passed checks (15 passed)
Check name Status Explanation
Title check ✅ Passed The title directly references the roadmap item (3.14.3) and clearly describes the main change: lowering target and action deps into implicit dependencies.
Description check ✅ Passed The description comprehensively explains the change scope, implementation details, review walkthrough, validation steps, and relates to the changeset throughout.
Docstring Coverage ✅ Passed Docstring coverage is 100.00% which is sufficient. The required threshold is 80.00%.
Linked Issues check ✅ Passed Check skipped because no linked issues were found for this pull request.
Out of Scope Changes check ✅ Passed Check skipped because no linked issues were found for this pull request.
Testing (Overall) ✅ Passed Tests are substantive: cycle detection verifies implicit_deps traversal; manifest tests confirm implicit_deps population; Ninja tests use exact output matching; BDD tests validate end-to-end lowering.
Module-Level Documentation ✅ Passed All 10 modified Rust modules carry docstrings clearly explaining purpose, utility and function as required by the check.
Testing (Unit And Behavioural) ✅ Passed Tests verify meaningful local behaviour (cycles, missing deps, edge cases), functional boundaries (BDD manifest→IR→Ninja), and integration across dependency combinations, substitution, and lowering.
Unit Architecture ✅ Passed All query paths remain pure reads. Command paths explicitly mutate. Dependencies are injected at boundaries. Fallibility is explicit via Result types. Tests properly separate concerns.
Domain Architecture ✅ Passed IR domain layer (BuildEdge, CycleDetector) cleanly separated from adapters; manifest lowered at clear boundary; Ninja translation isolated; no infrastructure details leak into domain.
Observability ✅ Passed Compile-time build-graph transformations introduced; existing cycle detection tracing and error reporting provide sufficient observability of implicit dependency traversal changes.
Security And Privacy ✅ Passed No security vulnerabilities found: type-safe path handling with Utf8PathBuf, safe template rendering via MiniJinja, proper string interpolation, no secrets or credentials exposed in code or tests.
Performance And Resource Use ✅ Passed No algorithmic regressions, unbounded allocations, or blocking operations. implicit_deps bounded by manifest; cycle detection O(nodes+edges); substitution linear; context clone at load time.
Concurrency And State ✅ Passed PR introduces no async, concurrency primitives, shared mutable state, or global state. All changes operate within single-threaded ownership boundaries using stack-local or owned structures.
Rust Compiler Lint Integrity ✅ Passed PR preserves compiler lint integrity: no improper suppressions added, all new test helpers are fully used, no artificial references, proper module scoping for test-only code.

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

✨ Finishing Touches
📝 Generate docstrings
  • Create stacked PR
  • Commit on current branch
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Commit unit tests in branch 3-14-3-lower-target-and-action-deps

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

Copy link
Copy Markdown
Contributor

@sourcery-ai sourcery-ai Bot left a comment

Choose a reason for hiding this comment

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

Sorry @leynos, you have reached your weekly rate limit of 2500000 diff characters.

Please try again later or upgrade to continue using Sourcery

codescene-delta-analysis[bot]

This comment was marked as outdated.

@leynos leynos changed the title Plan: lower target and action deps (3.14.3) Lower target and action deps (3.14.3) May 24, 2026
leynos added 8 commits May 24, 2026 14:54
Introduce the `BuildEdge.implicit_deps` field and initialise it as
empty at every existing construction site.

Keep behaviour unchanged for this step so the later manifest lowering,
cycle detection, and Ninja rendering changes can be reviewed separately.
Populate `BuildEdge.implicit_deps` from manifest `deps` while keeping
`sources` as the only explicit recipe input class.

Preserve documented `{{ ins }}` and `{{ outs }}` recipe placeholders
through manifest rendering so IR interpolation can substitute them from
explicit inputs and outputs before action hashing.
Traverse `BuildEdge.implicit_deps` alongside explicit inputs during IR
cycle analysis while continuing to ignore order-only dependencies.

Add regression coverage for implicit-only cycles, mixed cycles, missing
implicit dependencies, and bounded small cycles without adding a new test
dependency.
Emit `BuildEdge.implicit_deps` with Ninja's single-pipe separator between
explicit inputs and order-only dependencies.

Cover explicit, implicit-only, order-only, and phony action edge shapes
so the separator ordering remains stable.
Add an end-to-end manifest fixture that lowers target and action `deps`
into IR implicit deps and renders them as Ninja single-pipe dependencies.

Assert that recipe interpolation still receives only explicit `sources`,
including the documented `{{ ins }}` placeholder path.
Record that manifest `sources` become explicit recipe inputs, `deps`
become implicit dependencies, and order-only dependencies remain outside
cycle detection.

Align the user, developer, design, and formal-verification notes with the
implemented IR and Ninja dependency classes.
Record the final deterministic validation state before CodeRabbit review
and note the existing Markdown formatting exception that prevents using
`make fmt` as an all-or-nothing gate for this branch.
Mark the implicit dependency lowering roadmap item complete after the
implementation, deterministic gates, and CodeRabbit review all passed.
Update the ExecPlan with the final review and validation state.
codescene-delta-analysis[bot]

This comment was marked as outdated.

Mark the ExecPlan complete after pushing the branch and refreshing the
draft pull request with the final validation and reviewer context.
codescene-delta-analysis[bot]

This comment was marked as outdated.

@leynos
Copy link
Copy Markdown
Owner Author

leynos commented May 24, 2026

@coderabbitai Please suggest a fix for this issue and supply a prompt for an AI coding agent to enable it to apply the fix. Include the file and symbol names indicated in the issue at the head of your response.

Code Duplication

src/ir/cycle.rs:

What lead to degradation?

The module contains 2 functions with similar structure: tests.cycle_detector_records_missing_dependencies,tests.cycle_detector_records_missing_implicit_dependencies

Why does this problem occur?

Duplicated code often leads to code that's harder to change since the same logical change has to be done in multiple functions. More duplication gives lower code health.

How to fix it?

A certain degree of duplicated code might be acceptable. The problems start when it is the same behavior that is duplicated across the functions in the module, ie. a violation of the Don't Repeat Yourself (DRY) principle. DRY violations lead to code that is changed together in predictable patterns, which is both expensive and risky. DRY violations can be identified using CodeScene's X-Ray analysis to detect clusters of change coupled functions with high code similarity. Read More
Once you have identified the similarities across functions, look to extract and encapsulate the concept that varies into its own function(s). These shared abstractions can then be re-used, which minimizes the amount of duplication and simplifies change.

@coderabbitai

This comment was marked as resolved.

@leynos
Copy link
Copy Markdown
Owner Author

leynos commented May 25, 2026

@coderabbitai Please suggest a fix for this issue and supply a prompt for an AI coding agent to enable it to apply the fix. Include the file and symbol names indicated in the issue at the head of your response. Ensure that this is validated against the current version of the codegraph.

If further refinement to address this finding would be deleterious, please supply a clear explanatory one to two paragraph markdown message I can paste into the CodeScene web ui's diagnostic suppression function so this diagnostic can be silenced.

Code Duplication

src/ir/cycle.rs:

What lead to degradation?

The module contains 2 functions with similar structure: tests.cycle_detector_records_missing_dependencies,tests.cycle_detector_records_missing_implicit_dependencies

Why does this problem occur?

Duplicated code often leads to code that's harder to change since the same logical change has to be done in multiple functions. More duplication gives lower code health.

How to fix it?

A certain degree of duplicated code might be acceptable. The problems start when it is the same behavior that is duplicated across the functions in the module, ie. a violation of the Don't Repeat Yourself (DRY) principle. DRY violations lead to code that is changed together in predictable patterns, which is both expensive and risky. DRY violations can be identified using CodeScene's X-Ray analysis to detect clusters of change coupled functions with high code similarity. Read More
Once you have identified the similarities across functions, look to extract and encapsulate the concept that varies into its own function(s). These shared abstractions can then be re-used, which minimizes the amount of duplication and simplifies change.

@coderabbitai

This comment was marked as resolved.

Remove duplicated `CycleDetector` setup from the missing dependency tests
by sharing the assertion skeleton inside the cycle test module.
@leynos
Copy link
Copy Markdown
Owner Author

leynos commented May 26, 2026

@coderabbitai Please suggest a fix for this issue and supply a prompt for an AI coding agent to enable it to apply the fix. Include the file and symbol names indicated in the issue at the head of your response. Ensure that this is validated against the current version of the codegraph.

If further refinement to address this finding would be deleterious, please supply a clear explanatory one to two paragraph markdown message I can paste into the CodeScene web ui's diagnostic suppression function so this diagnostic can be silenced.

Code Duplication

src/ir/cycle.rs:

What lead to degradation?

The module contains 2 functions with similar structure: tests.cycle_detector_records_missing_dependencies,tests.cycle_detector_records_missing_implicit_dependencies

Why does this problem occur?

Duplicated code often leads to code that's harder to change since the same logical change has to be done in multiple functions. More duplication gives lower code health.

How to fix it?

A certain degree of duplicated code might be acceptable. The problems start when it is the same behavior that is duplicated across the functions in the module, ie. a violation of the Don't Repeat Yourself (DRY) principle. DRY violations lead to code that is changed together in predictable patterns, which is both expensive and risky. DRY violations can be identified using CodeScene's X-Ray analysis to detect clusters of change coupled functions with high code similarity. Read More
Once you have identified the similarities across functions, look to extract and encapsulate the concept that varies into its own function(s). These shared abstractions can then be re-used, which minimizes the amount of duplication and simplifies change.

@coderabbitai

This comment was marked as resolved.

codescene-delta-analysis[bot]

This comment was marked as outdated.

@leynos
Copy link
Copy Markdown
Owner Author

leynos commented May 26, 2026

@coderabbitai Please investigate the cause of the following issue using codegraph exploration and research, identify a fix and provide an AI coding agent prompt for the fix:

error: this argument is passed by value, but not consumed in the function body
Error:    --> src/ir/cycle.rs:179:18
    |
179 |         targets: HashMap<Utf8PathBuf, BuildEdge>,
    |                  ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
    |
    = help: for further information visit https://rust-lang.github.io/rust-clippy/master/index.html#needless_pass_by_value
    = note: requested on the command line with `-D clippy::needless-pass-by-value`
help: consider taking a reference instead
    |
179 |         targets: &HashMap<Utf8PathBuf, BuildEdge>,
    |                  +

error: could not compile `netsuke` (lib test) due to 1 previous error
warning: build failed, waiting for other jobs to finish...
make: *** [Makefile:32: lint] Error 101
Error: Process completed with exit code 2.

@coderabbitai

This comment was marked as resolved.

Pass the test target map by reference so Clippy does not flag the helper
for taking ownership of data it only inspects.
@leynos leynos marked this pull request as ready for review May 27, 2026 19:33
@sourcery-ai
Copy link
Copy Markdown
Contributor

sourcery-ai Bot commented May 27, 2026

Reviewer's Guide

Implements roadmap task 3.14.3 by introducing an explicit implicit-dependency class (implicit_deps) on IR build edges, wiring manifest deps into it, making cycle detection traverse it, emitting it as Ninja implicit dependencies, and updating tests and docs to reflect the new dependency and recipe-interpolation contracts.

File-Level Changes

Change Details Files
Extend the IR build graph to represent implicit dependencies separately from explicit inputs.
  • Add implicit_deps: Vec<Utf8PathBuf> to BuildEdge in the IR graph, documented as implicit dependencies that do not enter recipes.
  • Update all BuildEdge construction sites in tests and doctests to initialise implicit_deps (usually Vec::new()).
  • Clarify the BuildEdge struct documentation in docs/netsuke-design.md to describe inputs as explicit recipe inputs and implicit_deps as Ninja `
deps kept separate from$in/{{ ins }}`.
Lower manifest-level deps into the new IR implicit-dependency field without affecting explicit recipe inputs or interpolation.
  • In manifest-to-IR lowering, convert Target.deps to paths and populate BuildEdge.implicit_deps alongside existing inputs and order_only_deps.
  • Add unit tests to assert that deps from both targets and actions populate implicit_deps, that inputs reflect only sources, and that phony flags are preserved.
  • Ensure manifest rendering preserves {{ ins }} and {{ outs }} placeholders by wrapping recipe rendering in a helper that injects default placeholder values, so later IR interpolation can substitute them correctly from explicit inputs/outputs.
  • Add tests to verify that recipe commands interpolate $in and {{ ins }} using only sources, and that deps never appear in the command text while still populating implicit_deps.
  • Add BDD steps and a manifest fixture to assert graph-level inputs vs implicit_deps for targets and actions and to verify Ninja output and recipe interpolation behaviour end-to-end.
src/ir/from_manifest.rs
tests/ir_from_manifest_tests.rs
src/manifest/render.rs
tests/bdd/steps/ir.rs
tests/features/ninja.feature
tests/data/implicit_deps.yml
Update cycle detection so that implicit dependencies participate in cycle discovery and missing-dependency reporting.
  • Change the cycle detector to traverse both inputs and implicit_deps when visiting dependencies of a node.
  • Refactor and extend cycle tests to construct edges with both classes, including helpers to build edges, insert cycle edges, and assert missing dependencies.
  • Add tests for cycles that use only implicit deps, mixed explicit/implicit cycles, and bounded cycles of varying lengths where one edge uses an implicit dep, ensuring they are all detected.
  • Add tests ensuring missing dependencies are recorded for both explicit and implicit deps without affecting other behaviour.
src/ir/cycle.rs
Emit Ninja build lines that include implicit dependencies in the correct position and document the dependency-class and cycle contracts.
  • Update Ninja edge formatting to render `
<implicit_deps>after explicit inputs but before

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

Copy link
Copy Markdown
Contributor

@sourcery-ai sourcery-ai Bot left a comment

Choose a reason for hiding this comment

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

Hey - I've found 4 issues

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

## Individual Comments

### Comment 1
<location path="src/ir/cmd_interpolate.rs" line_range="142-146" />
<code_context>
     let ins_joined = ins.join(" ");
     let outs_joined = outs.join(" ");
-    let mut out = String::with_capacity(template.len());
+    let placeholder_template = template
+        .replace("{{ ins }}", &ins_joined)
+        .replace("{{ outs }}", &outs_joined);
+    let chars: Vec<char> = placeholder_template.chars().collect();
+    let mut out = String::with_capacity(placeholder_template.len());
     let mut in_backticks = false;
     let mut i = 0;
</code_context>
<issue_to_address>
**issue (bug_risk):** Template-level replacement of `{{ ins }}`/`{{ outs }}` ignores backtick-protected regions, unlike the rest of the interpolation logic.

Because the `.replace("{{ ins }}", …).replace("{{ outs }}", …)` runs before the backtick-aware scan, these placeholders now get expanded even inside backticks, unlike other substitutions that respect backtick-delimited “no interpolation” regions.

To preserve the existing escape semantics, consider handling `{{ ins }}`/`{{ outs }}` inside the `find_substitution`/scanning loop (or otherwise applying the same backtick rules to them) rather than doing a global replace first.
</issue_to_address>

### Comment 2
<location path="src/manifest/render.rs" line_range="101-110" />
<code_context>
     env.render_str(tpl, ctx).with_context(what)
 }

+fn render_recipe_str_with(
+    env: &Environment,
+    tpl: &str,
+    ctx: &Vars,
+    what: impl FnOnce() -> String,
+) -> Result<String> {
+    let mut recipe_ctx = ctx.clone();
+    recipe_ctx
+        .entry("ins".into())
+        .or_insert_with(|| ManifestValue::String("{{ ins }}".into()));
+    recipe_ctx
+        .entry("outs".into())
+        .or_insert_with(|| ManifestValue::String("{{ outs }}".into()));
+    render_str_with(env, tpl, &recipe_ctx, what)
+}
+
</code_context>
<issue_to_address>
**question:** Defaulting `ins`/`outs` to `"{{ ins }}"`/`"{{ outs }}"` makes it hard to represent those literals in recipes.

Because `ins`/`outs` are injected as the literal strings `"{{ ins }}"`/`"{{ outs }}"` and then substituted, recipes have no way to emit those exact literals in their output, even when using constructs like `{% raw %}`. If supporting literal `{{ ins }}`/`{{ outs }}` is needed, consider a different internal marker or a way to opt out/override this behavior so there is an escape hatch.
</issue_to_address>

### Comment 3
<location path="docs/execplans/3-14-3-lower-target-and-action-deps.md" line_range="13" />
<code_context>
+## Purpose / big picture
+
+Roadmap item `3.14.3` closes a long-standing gap in Netsuke's manifest contract.
+Today the AST `Target` struct accepts a `deps` field, documentation already
+describes it as Ninja's implicit-dependency class, and the user guide shows
+authors writing `deps: my_app` and `deps: [build/utils.h]`. None of that flows
</code_context>
<issue_to_address>
**issue (review_instructions):** AST is introduced without being expanded; expand it on first use (for example, “Abstract Syntax Tree (AST)”).

The execplan introduces “AST” without defining it. To follow the acronym guideline, expand it at first mention, for example: “Today the Abstract Syntax Tree (AST) `Target` struct accepts a `deps` field…”.

<details>
<summary>Review instructions:</summary>

**Path patterns:** `**/*.md`

**Instructions:**
Define uncommon acronyms on first use.

</details>
</issue_to_address>

### Comment 4
<location path="docs/execplans/3-14-3-lower-target-and-action-deps.md" line_range="284" />
<code_context>
+  including the IR/Ninja module boundary.
+- `docs/ortho-config-users-guide.md`: configuration layering and localized
+  help support if any config or CLI surface changes are required.
+- `docs/rstest-bdd-users-guide.md`: BDD feature and step guidance.
+- `docs/rust-testing-with-rstest-fixtures.md`: fixture and parameterization
+  patterns for unit tests.
</code_context>
<issue_to_address>
**issue (review_instructions):** BDD is used without definition; please expand it on first use (for example, “behaviour-driven development (BDD)”).

The acronym “BDD” is introduced here without being defined. To comply with the acronym rule, expand it on first use, e.g. “behaviour-driven development (BDD) feature and step guidance.”

<details>
<summary>Review instructions:</summary>

**Path patterns:** `**/*.md`

**Instructions:**
Define uncommon acronyms on first use.

</details>
</issue_to_address>

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

Comment thread src/ir/cmd_interpolate.rs
Comment on lines +142 to +146
let placeholder_template = template
.replace("{{ ins }}", &ins_joined)
.replace("{{ outs }}", &outs_joined);
let chars: Vec<char> = placeholder_template.chars().collect();
let mut out = String::with_capacity(placeholder_template.len());
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

issue (bug_risk): Template-level replacement of {{ ins }}/{{ outs }} ignores backtick-protected regions, unlike the rest of the interpolation logic.

Because the .replace("{{ ins }}", …).replace("{{ outs }}", …) runs before the backtick-aware scan, these placeholders now get expanded even inside backticks, unlike other substitutions that respect backtick-delimited “no interpolation” regions.

To preserve the existing escape semantics, consider handling {{ ins }}/{{ outs }} inside the find_substitution/scanning loop (or otherwise applying the same backtick rules to them) rather than doing a global replace first.

Comment thread src/manifest/render.rs
Comment on lines +101 to +110
fn render_recipe_str_with(
env: &Environment,
tpl: &str,
ctx: &Vars,
what: impl FnOnce() -> String,
) -> Result<String> {
let mut recipe_ctx = ctx.clone();
recipe_ctx
.entry("ins".into())
.or_insert_with(|| ManifestValue::String("{{ ins }}".into()));
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

question: Defaulting ins/outs to "{{ ins }}"/"{{ outs }}" makes it hard to represent those literals in recipes.

Because ins/outs are injected as the literal strings "{{ ins }}"/"{{ outs }}" and then substituted, recipes have no way to emit those exact literals in their output, even when using constructs like {% raw %}. If supporting literal {{ ins }}/{{ outs }} is needed, consider a different internal marker or a way to opt out/override this behavior so there is an escape hatch.

## Purpose / big picture

Roadmap item `3.14.3` closes a long-standing gap in Netsuke's manifest contract.
Today the AST `Target` struct accepts a `deps` field, documentation already
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

issue (review_instructions): AST is introduced without being expanded; expand it on first use (for example, “Abstract Syntax Tree (AST)”).

The execplan introduces “AST” without defining it. To follow the acronym guideline, expand it at first mention, for example: “Today the Abstract Syntax Tree (AST) Target struct accepts a deps field…”.

Review instructions:

Path patterns: **/*.md

Instructions:
Define uncommon acronyms on first use.

including the IR/Ninja module boundary.
- `docs/ortho-config-users-guide.md`: configuration layering and localized
help support if any config or CLI surface changes are required.
- `docs/rstest-bdd-users-guide.md`: BDD feature and step guidance.
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

issue (review_instructions): BDD is used without definition; please expand it on first use (for example, “behaviour-driven development (BDD)”).

The acronym “BDD” is introduced here without being defined. To comply with the acronym rule, expand it on first use, e.g. “behaviour-driven development (BDD) feature and step guidance.”

Review instructions:

Path patterns: **/*.md

Instructions:
Define uncommon acronyms on first use.

@coderabbitai coderabbitai Bot added the Roadmap label May 27, 2026
Copy link
Copy Markdown
Contributor

@coderabbitai coderabbitai Bot left a comment

Choose a reason for hiding this comment

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

Actionable comments posted: 2

🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Inline comments:
In `@src/ir/cmd_interpolate.rs`:
- Around line 142-146: Add an inline comment in the substitute function
clarifying the two-stage preprocessing: before building chars/out, note that
Stage 1 unconditionally replaces the literal "{{ ins }}" and "{{ outs }}" with
joined values (placeholder_template), and Stage 2 (the following logic) performs
backtick-aware $in/$out substitution; place this comment immediately above the
placeholder_template assignment (near the ins_joined/outs_joined code) so
readers understand the pipeline and ordering.

In `@src/manifest/render.rs`:
- Around line 101-116: Add a doc comment to render_recipe_str_with describing
that it clones the supplied context (Vars) and guarantees template variables
"ins" and "outs" by injecting default placeholder strings "{{ ins }}" and "{{
outs }}" when they are absent, so MiniJinja rendering can reference them before
later Ninja substitution; place the comment immediately above the
render_recipe_str_with signature and mention Environment, Vars, and that
render_str_with is called to perform the rendering.
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: Organization UI

Review profile: ASSERTIVE

Plan: Pro Plus

Run ID: df0d8079-1ab0-4937-b753-b959a33b61c3

📥 Commits

Reviewing files that changed from the base of the PR and between 05c2994 and abb5dea.

📒 Files selected for processing (19)
  • docs/developers-guide.md
  • docs/execplans/3-14-3-lower-target-and-action-deps.md
  • docs/formal-verification-methods-in-netsuke.md
  • docs/netsuke-design.md
  • docs/roadmap.md
  • docs/users-guide.md
  • src/ir/cmd_interpolate.rs
  • src/ir/cycle.rs
  • src/ir/from_manifest.rs
  • src/ir/graph.rs
  • src/manifest/render.rs
  • src/ninja_gen.rs
  • tests/bdd/steps/ir.rs
  • tests/bdd_tests.rs
  • tests/data/implicit_deps.yml
  • tests/features/ninja.feature
  • tests/ir_from_manifest_tests.rs
  • tests/ir_tests.rs
  • tests/ninja_gen_tests.rs

Comment thread src/ir/cmd_interpolate.rs
Comment on lines +142 to +146
let placeholder_template = template
.replace("{{ ins }}", &ins_joined)
.replace("{{ outs }}", &outs_joined);
let chars: Vec<char> = placeholder_template.chars().collect();
let mut out = String::with_capacity(placeholder_template.len());
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

🧹 Nitpick | 🔵 Trivial | ⚡ Quick win

Consider adding a comment explaining the preprocessing stage.

The substitution now operates in two stages: first replacing {{ ins }} and {{ outs }} placeholders (lines 142-144), then performing the existing backtick-aware $in/$out substitution. Add an inline comment clarifying this two-stage pipeline and noting that the {{ }} placeholders are replaced unconditionally before backtick detection.

📝 Suggested inline comment
 fn substitute(template: &str, ins: &[String], outs: &[String]) -> String {
     let ins_joined = ins.join(" ");
     let outs_joined = outs.join(" ");
+    // Stage 1: Replace {{ ins }} and {{ outs }} placeholders unconditionally.
+    // Stage 2 (below) performs backtick-aware $in/$out substitution.
     let placeholder_template = template
         .replace("{{ ins }}", &ins_joined)
         .replace("{{ outs }}", &outs_joined);
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@src/ir/cmd_interpolate.rs` around lines 142 - 146, Add an inline comment in
the substitute function clarifying the two-stage preprocessing: before building
chars/out, note that Stage 1 unconditionally replaces the literal "{{ ins }}"
and "{{ outs }}" with joined values (placeholder_template), and Stage 2 (the
following logic) performs backtick-aware $in/$out substitution; place this
comment immediately above the placeholder_template assignment (near the
ins_joined/outs_joined code) so readers understand the pipeline and ordering.

Comment thread src/manifest/render.rs
Comment on lines +101 to +116
fn render_recipe_str_with(
env: &Environment,
tpl: &str,
ctx: &Vars,
what: impl FnOnce() -> String,
) -> Result<String> {
let mut recipe_ctx = ctx.clone();
recipe_ctx
.entry("ins".into())
.or_insert_with(|| ManifestValue::String("{{ ins }}".into()));
recipe_ctx
.entry("outs".into())
.or_insert_with(|| ManifestValue::String("{{ outs }}".into()));
render_str_with(env, tpl, &recipe_ctx, what)
}

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

🧹 Nitpick | 🔵 Trivial | ⚡ Quick win

Document the helper's ins/outs injection behaviour.

Add a doc comment explaining that render_recipe_str_with ensures ins and outs template variables are always available by injecting default placeholder strings ("{{ ins }}" and "{{ outs }}") when absent, allowing recipe commands to reference these variables during MiniJinja rendering before subsequent Ninja substitution.

📝 Suggested doc comment
+/// Render a recipe command template with guaranteed `ins` and `outs` variables.
+///
+/// Clones the provided context and injects default `"{{ ins }}"` and `"{{ outs }}"`
+/// placeholder strings if not already present, ensuring recipe commands can always
+/// reference these variables during MiniJinja rendering. The placeholders are later
+/// substituted with actual paths during command interpolation.
 fn render_recipe_str_with(
     env: &Environment,
     tpl: &str,
     ctx: &Vars,
     what: impl FnOnce() -> String,
 ) -> Result<String> {
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@src/manifest/render.rs` around lines 101 - 116, Add a doc comment to
render_recipe_str_with describing that it clones the supplied context (Vars) and
guarantees template variables "ins" and "outs" by injecting default placeholder
strings "{{ ins }}" and "{{ outs }}" when they are absent, so MiniJinja
rendering can reference them before later Ninja substitution; place the comment
immediately above the render_recipe_str_with signature and mention Environment,
Vars, and that render_str_with is called to perform the rendering.

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