Skip to content

Improve YAML error reporting with miette#129

Merged
leynos merged 10 commits intomainfrom
codex/improve-yaml-error-message-clarity-wtfig1
Aug 19, 2025
Merged

Improve YAML error reporting with miette#129
leynos merged 10 commits intomainfrom
codex/improve-yaml-error-message-clarity-wtfig1

Conversation

@leynos
Copy link
Owner

@leynos leynos commented Aug 18, 2025

Summary

  • use miette and serde_spanned to highlight YAML parse errors with spans and actionable hints
  • document miette-based diagnostics in design guidelines

Testing

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

closes #49


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

Summary by Sourcery

Introduce miette-powered diagnostics for YAML parsing and migrate existing error handling to miette across the codebase

New Features:

  • Add span-aware YAML parsing errors with YamlDiagnostic producing precise source labels and suggestions
  • Provide actionable hints for common YAML mistakes like tab indentation, missing list items, or missing colons

Enhancements:

  • Replace anyhow contexts with miette’s IntoDiagnostic and wrap_err in manifest parsing, Jinja rendering, file I/O, and Ninja runner
  • Add render_str_with helper to unify Jinja template error reporting
  • Refactor manifest parsing to accept named sources for better diagnostic context

Build:

  • Add miette and strip-ansi-escapes dependencies to Cargo.toml

Documentation:

  • Document miette-based diagnostics in design guidelines and update crate selection table to include miette

Tests:

  • Add regression tests for YAML parse errors verifying line/column spans and hints
  • Update runner, cucumber, and AST tests to assert miette-style error chains

@sourcery-ai
Copy link
Contributor

sourcery-ai bot commented Aug 18, 2025

Reviewer's Guide

This PR integrates miette-based diagnostics in YAML parsing to emit precise spans and actionable hints, standardizes error wrapping with miette across parsing and rendering workflows, updates documentation to reflect the new diagnostics strategy, and adds the necessary dependencies for runtime and testing.

Sequence diagram for YAML parsing with miette diagnostics

sequenceDiagram
    participant User
    participant manifest
    participant serde_yml
    participant miette

    User->>manifest: from_str(yaml)
    manifest->>serde_yml: from_str(yaml)
    alt Parse error
        serde_yml-->>manifest: YamlError
        manifest->>miette: map_yaml_error(err, yaml, name)
        miette-->>manifest: Report<YamlDiagnostic>
        manifest-->>User: Error with span and hint
    else Success
        serde_yml-->>manifest: YamlValue
        manifest-->>User: NetsukeManifest
    end
Loading

File-Level Changes

Change Details Files
Integrate miette-based diagnostics for YAML parse errors
  • Added to_span, YamlDiagnostic, hint_for, and map_yaml_error functions
  • Refactored from_str into from_str_named to map serde_yml errors through miette
  • Introduced regression tests in yaml_error_tests.rs to validate spans and hints
src/manifest.rs
tests/yaml_error_tests.rs
Standardize miette error handling across codebase
  • Replaced .context/.with_context and anyhow errors with .into_diagnostic() and .wrap_err/_with
  • Created render_str_with helper to unify Jinja rendering error labels
  • Updated runner logic and existing tests to use miette diagnostics
src/manifest.rs
src/runner.rs
tests/runner_tests.rs
tests/steps/ir_steps.rs
tests/ast_tests.rs
Update documentation to include miette diagnostics
  • Augmented design guidelines to document miette’s span-based diagnostics
  • Extended the dependency table to list miette alongside anyhow and thiserror
docs/netsuke-design.md
Add miette and ANSI stripping dependencies
  • Added miette (with fancy feature) and strip-ansi-escapes to Cargo.toml
  • Updated Cargo.lock to capture new test-only dependencies
Cargo.toml
Cargo.lock

Assessment against linked issues

Issue Objective Addressed Explanation
#49 Provide YAML syntax error messages that include specific line and column numbers where the error occurred.
#49 Include a description of the type of syntax error encountered in YAML error messages.
#49 Offer suggestions for how to fix common YAML syntax issues in error messages.

Possibly linked issues


Tips and commands

Interacting with Sourcery

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

Customizing Your Experience

Access your dashboard to:

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

Getting Help

@coderabbitai
Copy link
Contributor

coderabbitai bot commented Aug 18, 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 4 minutes and 13 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 dbc4b1e and 988dbc1.

📒 Files selected for processing (3)
  • src/manifest.rs (11 hunks)
  • tests/runner_tests.rs (1 hunks)
  • tests/steps/ir_steps.rs (2 hunks)

Summary by CodeRabbit

  • New Features

    • Rich, location-aware diagnostics for manifest/YAML parsing with clearer messages and helpful hints (e.g., line/column, indentation, missing symbols).
    • Improved error propagation across build and run flows for more readable reports.
  • Documentation

    • Expanded error-handling guidance, including updated technology stack and examples of user-facing diagnostics.
  • Tests

    • Added regression tests validating YAML error diagnostics and updated existing tests to assert clearer error surfaces.
  • Chores

    • Added dependencies to support enhanced diagnostics and test utilities.

Walkthrough

Introduce miette-based structured diagnostics and YAML-specific hinting into manifest parsing, update tests to assert rendered diagnostics (stripping ANSI in CI), add docs referencing miette and diag-json, and add two dependencies (miette and strip-ansi-escapes).

Changes

Cohort / File(s) Summary
Dependencies
Cargo.toml
Add miette = { version = "7.6.0", features = ["fancy"] } to [dependencies] and strip-ansi-escapes = "0.2" to [dev-dependencies].
Manifest diagnostics
src/manifest.rs
Replace context-based anyhow errors with miette diagnostics: add YamlDiagnostic and helpers (to_span, hint_for, map_yaml_error), introduce from_str_named, use .into_diagnostic() / .wrap_err*() to produce named-source, span-aware Reports while preserving public signatures.
Runtime error mapping
src/runner.rs
Switch internal error propagation to miette (IntoDiagnostic, wrap_err/wrap_err_with) across ninja/tempfile/manifest I/O and graph steps. Public APIs unchanged.
IR example / docs
docs/netsuke-design.md, src/ir.rs
Update design doc to include miette and diag-json; add illustrative IrGenError example derived from thiserror for docs/example purposes.
Tests — YAML diagnostics
tests/yaml_error_tests.rs
Add regression tests rendering miette GraphicalReportHandler output, normalise via strip_ansi_escapes, and assert presence of line/column info and hint strings for several malformed-YAML cases.
Tests — adaptions
tests/ast_tests.rs, tests/runner_tests.rs, tests/steps/ir_steps.rs
Update tests to use miette::Result or IntoDiagnostic and adjust assertions to match new diagnostic wrapping and top-level/source error expectations.

Sequence Diagram(s)

sequenceDiagram
  participant Caller as Caller
  participant Manifest as manifest::from_str_named
  participant Serde as serde_yaml::from_str
  participant Mapper as map_yaml_error
  participant Reporter as miette::Report

  Caller->>Manifest: from_str(yaml) / from_str_named(yaml,name)
  Manifest->>Serde: parse(yaml)
  alt Parse success
    Serde-->>Manifest: Ok(deserialised)
    Manifest-->>Caller: Ok(manifest)
  else Parse failure
    Serde-->>Manifest: Err(YamlError{ loc, msg })
    Manifest->>Mapper: map_yaml_error(err, source, name)
    Mapper->>Reporter: build Report with NamedSource, SourceSpan, hints
    Mapper-->>Manifest: Err(Report)
    Manifest-->>Caller: Err(Report) (renderable by GraphicalReportHandler)
  end
Loading

Estimated code review effort

🎯 3 (Moderate) | ⏱️ ~25 minutes

Assessment against linked issues

Objective Addressed Explanation
Include specific line and column numbers for YAML syntax errors [#49]
Provide descriptive error messages for the syntax issue encountered [#49]
Suggest fixes for common YAML mistakes (tabs, missing colon) [#49]

Assessment against linked issues: Out-of-scope changes

Code Change Explanation
Add IrGenError public enum/example (src/ir.rs) Documentation/example IR error enum is unrelated to improving YAML parse diagnostics required by #49.
Change runtime error handling in src/runner.rs (multiple locations) Switching runner internals to miette diagnostics improves overall error consistency but is not required for the YAML syntax‑error objectives in #49; scope differs from the linked issue.

Poem

Tab and colon once conspired to hide,
Now miette paints the spot and stride.
Show the line, the column, and a clue—
Fix the YAML, let the build run true. 🎉

✨ 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/improve-yaml-error-message-clarity-wtfig1

🪧 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.

Copy link
Contributor

@sourcery-ai sourcery-ai bot left a comment

Choose a reason for hiding this comment

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

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

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

### Comment 1
<location> `tests/yaml_error_tests.rs:3` </location>
<code_context>
+use netsuke::manifest;
+
+#[test]
+fn reports_line_and_column_with_tab_hint() {
+    let yaml = "targets:\n\t- name: test\n";
</code_context>

<issue_to_address>
Consider adding a test for YAML parse errors without a specific hint.

Adding a test for generic YAML parse errors will help verify that error reporting remains informative even when no custom hint is triggered.
</issue_to_address>

### Comment 2
<location> `tests/yaml_error_tests.rs:16` </location>
<code_context>
+}
+
+#[test]
+fn suggests_colon_when_missing() {
+    let yaml = "targets:\n  - name: hi\n    command echo\n";
+    let err = manifest::from_str(yaml).expect_err("parse should fail");
+    let msg = err.to_string();
+    assert!(msg.contains("line 3"), "missing line info: {msg}");
+    assert!(msg.contains("expected ':'"), "missing error detail: {msg}");
+    assert!(
+        msg.contains("Ensure each key is followed by ':'"),
+        "missing suggestion: {msg}"
+    );
+}
</code_context>

<issue_to_address>
Consider testing for other actionable hints, such as missing dash for list items.

Add a test case for a list item missing a dash to confirm the hint is triggered as expected.
</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.

Copy link
Contributor

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

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

Actionable comments posted: 4

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

1-27: Consolidate into parameterised rstest cases and harden assertions.

Replace near-duplicate tests with a #[rstest] table and assert on stable substrings (location, primary cause, and hint). This reduces duplication and aligns with test guidelines.

Apply a refactor similar to:

use netsuke::manifest;
use rstest::rstest;

#[rstest]
#[case("targets:\n\t- name: test\n", &["line 2, column 1", "Use spaces for indentation"])]
#[case("targets:\n  - name: hi\n    command echo\n",
       &["line 3", "expected ':'", "Ensure each key is followed by ':'"])]
fn yaml_diagnostics_are_actionable(#[case] yaml: &str, #[case] needles: &[&str]) {
    let err = manifest::from_str(yaml).expect_err("parse should fail");
    let msg = err.to_string();
    for needle in needles {
        assert!(msg.contains(needle), "missing: {needle}\nmessage: {msg}");
    }
}
📜 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 a1b648b and 6f6908d.

⛔ Files ignored due to path filters (1)
  • Cargo.lock is excluded by !**/*.lock
📒 Files selected for processing (4)
  • Cargo.toml (1 hunks)
  • docs/netsuke-design.md (3 hunks)
  • src/manifest.rs (2 hunks)
  • tests/yaml_error_tests.rs (1 hunks)
🧰 Additional context used
📓 Path-based instructions (6)
**/*.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/yaml_error_tests.rs
  • src/manifest.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/yaml_error_tests.rs
  • src/manifest.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/yaml_error_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
Cargo.toml

📄 CodeRabbit Inference Engine (AGENTS.md)

Cargo.toml: Use explicit version ranges and keep dependencies up to date
Mandate SemVer caret requirements for all dependencies (e.g., "1.2.3")
Prohibit wildcard (*) and open-ended (>=) version specifiers; use ~ only with a specific, documented reason

Files:

  • Cargo.toml
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
🧬 Code Graph Analysis (1)
tests/yaml_error_tests.rs (1)
src/manifest.rs (1)
  • from_str (92-113)
🔍 Remote MCP

Here’s the key additional context to assist your review:

  1. Issue #49 (“Add friendlier YAML syntax error messages”) calls for replacing opaque parse failures with messages that name
    – What happened
    – Where (line & column)
    – Why (error type)
    – How to fix (actionable hints)
    [::functions.ask_question::]

  2. In src/manifest.rs, YAML parse errors are now converted via map_yaml_error into a YamlDiagnostic (miette Diagnostic) that:
    – Attaches a SourceSpan from serde_spanned::Spanned
    – Includes a hint via hint_for for common YAML issues:
    • Tab indentation (suggest “use spaces”)
    • Missing list dash (“ensure each list item starts with ‘-’”)
    • Missing colon (“ensure each key is followed by ‘:’”)
    – Wraps into anyhow::Error with code netsuke.yaml.parse

  3. tests/yaml_error_tests.rs validate that error messages include both:
    – The precise “line X, column Y” location
    – The corresponding hint text (e.g. “Use spaces for indentation” or “Ensure each key is followed by ‘:’”)

  4. docs/netsuke-design.md and docs/netsuke-design.md’s technology-summary table were updated to document miette + serde_spanned usage and a new diag-json option for machine-friendly error output.

This confirms the PR addresses each objective from #49 by adding span-aware diagnostics, descriptive messages, and actionable hints.

⏰ 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 (5)
src/manifest.rs (3)

9-17: Adopt miette/serde_spanned imports cleanly; keep error context uniform.

The new imports and constants align with the diagnostics strategy. Keep using anyhow::Context for higher-level wrapping to maintain uniform error chains.


20-31: Compute a minimal, robust span; LGTM.

The span calculation guards against out-of-bounds and avoids highlighting newlines. This is a sensible default for pinpointing parse locations.


93-93: Maintain structured parse errors; LGTM.

Mapping serde_yml::from_str errors through map_yaml_error meets the friendly-error requirements. Keep this path the same when introducing the filename threading suggested above.

docs/netsuke-design.md (2)

1570-1579: Document the error‑reporting stack succinctly; LGTM.

The technology summary reflects miette + serde_spanned correctly. Keep this table unwrapped as required by the docs policy.


1272-1279: Wrap the “7.2” paragraph at 80 columns and maintain en-GB spelling

Wrap the new paragraph under “7.2 Crate Selection and Strategy” so that no line exceeds 80 characters, using en-GB spelling.
Leave the heading and any tables unwrapped.
Manually run your project’s markdownlint command to confirm there are no lint errors.

codescene-delta-analysis[bot]

This comment was marked as outdated.

Copy link
Contributor

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

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

Actionable comments posted: 4

♻️ Duplicate comments (6)
src/manifest.rs (5)

304-308: Propagate the actual file name from from_path.

Do not lose the filename. Add a small helper and use the path’s display name when mapping YAML errors.

Outside this hunk, add:

// New helper
pub fn from_str_named(yaml: &str, name: &str) -> Result<NetsukeManifest> {
    let mut doc: YamlValue =
        serde_yml::from_str(yaml).map_err(|e| map_yaml_error(&e, yaml, name))?;

    let mut env = Environment::new();
    env.set_undefined_behavior(UndefinedBehavior::Strict);

    if let Some(vars) = doc.get("vars").and_then(|v| v.as_mapping()).cloned() {
        for (k, v) in vars {
            let key = k
                .as_str()
                .ok_or_else(|| anyhow!("non-string key in 'vars' mapping: {k:?}"))?
                .to_string();
            env.add_global(key, Value::from_serialize(v));
        }
    }

    expand_foreach(&mut doc, &env)?;
    let manifest: NetsukeManifest = serde_yml::from_value(doc).context(ERR_MANIFEST_PARSE)?;
    render_manifest(manifest, &env)
}

Then update this function:

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

31-46: Fix misuse of #[diagnostic] and drop dynamic label field.

Attach #[diagnostic(...)] to the struct, not to a field, and remove the dynamic label: String. miette expects a literal in #[label("…")]. Also remove #[doc(hidden)] from a public type and document it per guidelines.

Apply:

 #[derive(Debug, Error, Diagnostic)]
-#[error("{message}")]
-#[doc(hidden)]
-pub struct YamlDiagnostic {
+/// Diagnostic for YAML parse errors in Netsuke manifests.
+///
+/// Includes a precise span, a readable message, and optional actionable help.
+#[error("{message}")]
+#[diagnostic(code(netsuke.yaml.parse))]
+pub struct YamlDiagnostic {
     #[source_code]
     src: NamedSource,
-    #[label("{label}")]
+    #[label("parse error here")]
     span: Option<SourceSpan>,
     #[help]
     help: Option<String>,
     #[source]
     source: Option<anyhow::Error>,
-    #[diagnostic(code(netsuke.yaml.parse))]
     message: String,
-    label: String,
 }

48-59: Scope the “tabs are invalid” hint to indentation near the fault.

Avoid emitting the tabs hint for any tab anywhere in the file. Limit the check to leading indentation on the fault line. Thread the optional location into hint_for.

Apply:

-fn hint_for(err_str: &str, src: &str) -> Option<String> {
+fn hint_for(err_str: &str, src: &str, loc: Option<Location>) -> Option<String> {
     let lower = err_str.to_lowercase();
-    if src.contains('\t') {
-        Some("Use spaces for indentation; tabs are invalid in YAML.".into())
-    } else if lower.contains("did not find expected '-'") {
+    if let Some(loc) = loc {
+        // Only inspect leading indentation on the fault line.
+        let idx = loc.index();
+        let line_start = src[..idx].rfind('\n').map(|i| i + 1).unwrap_or(0);
+        let line_end = src[idx..].find('\n').map(|i| idx + i).unwrap_or(src.len());
+        let line = &src[line_start..line_end];
+        let mut saw_tab_in_indent = false;
+        for ch in line.chars() {
+            if ch == ' ' {
+                continue;
+            } else if ch == '\t' {
+                saw_tab_in_indent = true;
+                break;
+            } else {
+                break; // first non-whitespace
+            }
+        }
+        if saw_tab_in_indent {
+            return Some("Use spaces for indentation; tabs are invalid in YAML.".into());
+        }
+    }
+    if lower.contains("did not find expected '-'") {
         Some("Start list items with '-' and ensure proper indentation.".into())
     } else if lower.contains("expected ':'") {
         Some("Ensure each key is followed by ':' separating key and value.".into())
     } else {
         None
     }
 }

61-79: Propagate filename and thread location into hints; tighten diagnostic construction.

Name the NamedSource with the actual manifest path for better UX in IDE/CI, and pass the location into hint_for. Also drop the now-removed label assignment.

Apply:

-fn map_yaml_error(err: &YamlError, src: &str) -> anyhow::Error {
+fn map_yaml_error(err: &YamlError, src: &str, name: &str) -> anyhow::Error {
     let (line, col, span) = err.location().map_or((1, 1, None), |l| {
         (l.line(), l.column(), Some(to_span(src, l)))
     });
     let err_str = err.to_string();
-    let hint = hint_for(&err_str, src);
+    let hint = hint_for(&err_str, src, err.location());
     let message = format!("YAML parse error at line {line}, column {col}: {err_str}");

     let diag = YamlDiagnostic {
-        src: NamedSource::new("manifest.yml", src.to_string()),
+        src: NamedSource::new(name, src.to_string()),
         span,
         help: hint,
         source: Some(anyhow::Error::msg(err_str.clone())),
         message,
-        label: "parse error here".into(),
     };

     anyhow::Error::new(diag)
 }

90-90: Thread a sensible name for in-memory manifests.

Pass a default name so diagnostics aren’t stuck with a placeholder later.

Apply:

-    let mut doc: YamlValue = serde_yml::from_str(yaml).map_err(|e| map_yaml_error(&e, yaml))?;
+    let mut doc: YamlValue =
+        serde_yml::from_str(yaml).map_err(|e| map_yaml_error(&e, yaml, "Netsukefile"))?;
tests/yaml_error_tests.rs (1)

1-40: Add missing cases from prior suggestions.

Add tests for:

  • A list item missing a dash to verify the “missing '-'” hint.
  • A generic parse error without a specific hint to ensure the baseline message remains informative.

I can draft these tests if you want them in this PR.

📜 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 6f6908d and 8af78d8.

📒 Files selected for processing (2)
  • src/manifest.rs (2 hunks)
  • tests/yaml_error_tests.rs (1 hunks)
🧰 Additional context used
📓 Path-based instructions (3)
**/*.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/yaml_error_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/yaml_error_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
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/yaml_error_tests.rs
🧬 Code Graph Analysis (2)
src/manifest.rs (1)
tests/yaml_error_tests.rs (2)
  • err (8-9)
  • err (26-27)
tests/yaml_error_tests.rs (1)
src/manifest.rs (1)
  • from_str (89-110)
🔍 Remote MCP Context7, Deepwiki

Relevant findings to aid PR review:

  • CI runs on PRs to main and executes (in order) make check-fmt, make lint, make test, and coverage steps; snapshot tests run with INSTA_UPDATE=no so snapshot mismatches fail CI.,

  • Make targets / commands used locally and in CI: make fmt / make check-fmt (cargo fmt), make lint (cargo clippy with -D warnings), make test (cargo test --all-targets --all-features), and cargo tarpaulin for coverage. Reviewer should run these locally before approving.

  • YAML-parsing–related tests live and run in:

    • tests/ast_tests.rs (unit tests for NetsukeManifest deserialization)
    • tests/features/manifest.feature (BDD scenarios via cucumber-rs)
    • New tests in this PR: tests/yaml_error_tests.rs (validates line/column and hint text)
      Failures in these files will cause the Test step to fail.
  • CI will surface any YAML-parsing failures with details in the Test job logs (line/column, message); snapshot mismatches are treated as failures in CI.,

Sources:,,

⏰ 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 (1)
tests/yaml_error_tests.rs (1)

1-3: Avoid coupling tests to the concrete diagnostic type.

Relying on YamlDiagnostic being pub ties the external test crate to an internal type. Expose a small test-only helper that returns miette::Report or re-export a trait object (pub type ManifestDiag = dyn Diagnostic;) to decouple, or keep YamlDiagnostic documented and stable.

Do you want me to open a follow-up to add a pub fn render_yaml_error(err: &anyhow::Error) -> String helper behind cfg(test) so tests don’t need to downcast?

codescene-delta-analysis[bot]

This comment was marked as outdated.

@leynos
Copy link
Owner Author

leynos commented Aug 18, 2025

@coderabbitai review

@coderabbitai
Copy link
Contributor

coderabbitai bot commented Aug 18, 2025

✅ Actions performed

Review triggered.

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

Copy link
Contributor

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

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

Actionable comments posted: 2

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

6-13: Add cases for “missing dash” and a generic parser error to cover the remaining hint paths.

Extend the parameterised matrix to exercise:

  • Missing dash for list items under a sequence key (e.g. targets).
  • A generic YAML parse error without a tailored hint.

This aligns with the PR objectives and prior review feedback.

Example additions (adjust needles to your exact hint text):

 #[rstest]
 #[case(
     "targets:\n\t- name: test\n",
     &["line 2, column 1", "Use spaces for indentation"],
 )]
 #[case(
     "targets:\n  - name: hi\n    command echo\n",
     &["line 3", "expected ':'", "Ensure each key is followed by ':'"],
 )]
+#[case(
+    // Missing dash for list items: mapping where a sequence is expected
+    "targets:\n  name: hi\n  command: echo\n",
+    &[
+        "targets",            // context key
+        "list",               // error context mentions list/sequence
+        "-",                  // hint mentions a dash
+    ],
+)]
+#[case(
+    // Generic malformed YAML to exercise the fallback diagnostic
+    "targets: [\n",
+    &[
+        "line 1",             // location
+        "parse error",        // generic wording from parser path
+    ],
+)]
 fn yaml_diagnostics_are_actionable(#[case] yaml: &str, #[case] needles: &[&str]) {

If you want, I can wire in exact substrings from the implemented hints to remove guesswork.


15-18: Remove the downcast; expose a parsing API that returns a miette report.

Downcasting to a concrete diagnostic couples the test to internals and was previously flagged. Expose a helper that returns a miette::Report and render that directly.

Apply in test:

-    let err = manifest::from_str(yaml).expect_err("parse should fail");
-    let diag = err
-        .downcast_ref::<manifest::YamlDiagnostic>()
-        .expect("diagnostic type");
+    let report = manifest::from_str_report(yaml).unwrap_err();

And update rendering (see next comment) to pass &report where &dyn Diagnostic is expected.

Outside this file, add a test-only-friendly API in src/manifest.rs:

// New API preserving the existing public surface
pub fn from_str_report(yaml: &str) -> miette::Result<NetsukeManifest> {
    from_str(yaml).map_err(|e| {
        // Preserve structured YAML diagnostic if present; otherwise wrap.
        if let Some(diag) = e.downcast_ref::<YamlDiagnostic>() {
            // Clone or re-create the diagnostic as needed
            miette::Report::new(diag.clone())
        } else {
            miette::Report::msg(e.to_string())
        }
    })
}

If cloning the diagnostic isn’t available, return miette::Report::new(map_yaml_error(...)) by re-mapping from the original serde_yml::Error you have at the parsing site.

📜 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 8af78d8 and cd371b0.

📒 Files selected for processing (1)
  • tests/yaml_error_tests.rs (1 hunks)
🧰 Additional context used
📓 Path-based instructions (1)
**/*.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/yaml_error_tests.rs
🧬 Code Graph Analysis (1)
tests/yaml_error_tests.rs (1)
src/manifest.rs (1)
  • from_str (89-110)
🔍 Remote MCP Context7

Findings relevant to reviewing dependency additions:

  • miette resolved to Context7 library ID /zkat/miette (description: "Fancy extension for std::error::Error with pretty, detailed diagnostic printing"; trust score 9.9). Recommended: dependency miette = { version = "5", features = ["fancy"] } matches a high-quality crate.,

  • serde_spanned did not produce a clear one-to-one Context7 library match in the search results (search returned general Serde/Serde-related entries and Miette again). Recommend verifying the exact crate name and source (crates.io repository or GitHub) for serde_spanned used in Cargo.toml to ensure it maps to the intended crate.,

Tools used: Context7 resolve-library-id (via parallel call).

🔇 Additional comments (1)
tests/yaml_error_tests.rs (1)

23-25: LGTM: concise substring assertions and helpful failure context.

The loop-based asserts keep the test compact and the failure message is informative. Keep this pattern as additional cases are added.

codescene-delta-analysis[bot]

This comment was marked as outdated.

Copy link
Contributor

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

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

Actionable comments posted: 4

♻️ Duplicate comments (6)
src/manifest.rs (5)

9-15: Imports are tight and follow crate guidelines.

Keep miette::{Diagnostic, NamedSource, SourceSpan} scoped; serde_spanned was removed as advised previously.


30-44: Diagnostic struct is well-formed and concise.

Struct-level #[diagnostic(code(...))], static #[label("…")], and #[doc(hidden)] are all correctly applied. This enables precise spans and stable rendering.


46-76: Hint generation is scoped to the indentation run.

Limit the “tabs” hint to the leading whitespace of the faulting line; this avoids false positives in other parts of the file. Good.


78-96: Propagate filename and precise location in the user-facing message.

The message embeds line/column and the NamedSource uses the actual name. This improves IDE/CI annotations.


333-338: Include the actual path in diagnostics.

Passing path_ref.display().to_string() improves UX in reports and aligns with the testing strategy.

tests/yaml_error_tests.rs (1)

1-3: Add a module-level inner doc comment per test guidelines.

Document the test module’s purpose at the top.

+//! Tests for YAML diagnostics: validates spans, labels, and actionable hints.
 use miette::GraphicalReportHandler;
 use netsuke::manifest;
 use rstest::rstest;
📜 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 cd371b0 and e74e004.

⛔ Files ignored due to path filters (1)
  • Cargo.lock is excluded by !**/*.lock
📒 Files selected for processing (4)
  • Cargo.toml (1 hunks)
  • docs/netsuke-design.md (3 hunks)
  • src/manifest.rs (4 hunks)
  • tests/yaml_error_tests.rs (1 hunks)
🧰 Additional context used
📓 Path-based instructions (6)
Cargo.toml

📄 CodeRabbit Inference Engine (AGENTS.md)

Cargo.toml: Use explicit version ranges and keep dependencies up to date
Mandate SemVer caret requirements for all dependencies (e.g., "1.2.3")
Prohibit wildcard (*) and open-ended (>=) version specifiers; use ~ only with a specific, documented reason

Files:

  • Cargo.toml
**/*.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/yaml_error_tests.rs
  • src/manifest.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/yaml_error_tests.rs
  • src/manifest.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/yaml_error_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
🧬 Code Graph Analysis (2)
tests/yaml_error_tests.rs (1)
src/manifest.rs (1)
  • from_str (138-140)
src/manifest.rs (1)
tests/yaml_error_tests.rs (1)
  • err (30-31)
🔍 Remote MCP Context7

Findings (concise)

  • miette: resolves to Context7 library ID /zkat/miette — "Fancy extension for std::error::Error with pretty, detailed diagnostic printing", high trust score (9.9) and many code snippets (28). This aligns with the PR's added dependency (miette) and supports that it's a well-known crate for pretty diagnostics.,

  • serde_spanned: resolve returned no clear one-to-one match for a crate named serde_spanned; results instead surfaced general Serde entries (e.g., /serde-rs/serde, /websites/rs-crate-serde_yml). Recommendation: verify the exact crate name and source (crates.io/GitHub) and ensure the Cargo.toml entry matches the intended published crate.,

Tools used: Context7 resolve-library-id (via parallel call).,

⏰ 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 (4)
docs/netsuke-design.md (2)

1272-1282: Good addition: clarify the role of miette in the error stack.

The section accurately frames miette as the user-facing diagnostics layer and stays consistent with the module changes.


1570-1577: Table update aligns with the new diagnostics stack.

The “Error Handling” row now reflects anyhow + thiserror + miette, matching the implementation.

src/manifest.rs (2)

106-128: Thread diagnostics through parsing without sacrificing API stability.

from_str_named cleanly maps serde_yml errors to typed diagnostics and leaves the public signature unchanged.


130-141: Default source name is sensible.

Using "Netsukefile" as the default display name yields readable diagnostics for in-memory parsing.

codescene-delta-analysis[bot]

This comment was marked as outdated.

@leynos
Copy link
Owner Author

leynos commented Aug 19, 2025

@coderabbitai review

@coderabbitai
Copy link
Contributor

coderabbitai bot commented Aug 19, 2025

✅ Actions performed

Review triggered.

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

@leynos
Copy link
Owner Author

leynos commented Aug 19, 2025

@coderabbitai review

@coderabbitai
Copy link
Contributor

coderabbitai bot commented Aug 19, 2025

✅ Actions performed

Review triggered.

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

Copy link
Contributor

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

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

Actionable comments posted: 6

Caution

Some comments are outside the diff and can’t be posted inline due to platform limitations.

⚠️ Outside diff range comments (1)
Cargo.toml (1)

6-24: Remove serde_spanned references
No usage of serde_spanned found and it is not declared in Cargo.toml. Remove all mentions of serde_spanned integration from the PR description or accompanying documentation.

  • Delete serde_spanned references in the PR description or docs
♻️ Duplicate comments (2)
src/manifest.rs (1)

17-27: Fix SourceSpan length type conversion to avoid API breakage

Pass the length as the expected offset type. This was previously flagged; re-apply the fix to keep compatibility across miette versions.

Apply:

-    let len = end.saturating_sub(at);
-    SourceSpan::new(at.into(), len)
+    let len = end.saturating_sub(at);
+    SourceSpan::new(at.into(), len.into())
tests/yaml_error_tests.rs (1)

15-37: Parameterise YAML error cases (resolved as per prior review).

The rstest cases remove duplication and cover tabs, missing colons, missing dashes, and a generic parse error. This addresses earlier feedback to add a no-hint failure case.

📜 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 e74e004 and eab64f1.

⛔ Files ignored due to path filters (1)
  • Cargo.lock is excluded by !**/*.lock
📒 Files selected for processing (7)
  • Cargo.toml (2 hunks)
  • src/manifest.rs (11 hunks)
  • src/runner.rs (2 hunks)
  • tests/ast_tests.rs (1 hunks)
  • tests/runner_tests.rs (1 hunks)
  • tests/steps/ir_steps.rs (1 hunks)
  • tests/yaml_error_tests.rs (1 hunks)
🧰 Additional context used
📓 Path-based instructions (1)
**/*.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/runner_tests.rs
  • tests/ast_tests.rs
  • tests/steps/ir_steps.rs
  • tests/yaml_error_tests.rs
  • src/runner.rs
  • src/manifest.rs
🔍 Remote MCP Context7

Summary of additional relevant facts for reviewing the PR

  • miette is a known crate; Context7 match: /zkat/miette (description: "Fancy extension for std::error::Error with pretty, detailed diagnostic printing"; trust score 9.9). This aligns with the PR adding miette for rich diagnostics.

  • serde_spanned did not resolve to a clear, single Context7 library match (search returned general Serde-related results but no definitive "serde_spanned" entry). Recommend verifying the exact crate name/source (crates.io or GitHub) and that Cargo.toml uses the intended published crate.

  • strip-ansi-escapes did not return a clear Context7 match in the parallel search output. Confirm the dev-dependency name/version and source (crates.io) to ensure test normalization uses the intended crate.

Tools used

  • Context7 resolve-library-id (parallel calls for "miette", "serde_spanned", "strip-ansi-escapes") — functions.resolve-library-id invoked via multi_tool_use.parallel.
🔇 Additional comments (9)
src/manifest.rs (3)

45-75: LGTM: Scope the “tabs” hint to indentation and add targeted hints

The heuristics correctly probe indentation on the error line for tabs and add specific fix-ups for missing dashes and colons. Keep it.


77-95: LGTM: Map YAML errors to rich miette diagnostics

The diagnostic includes a NamedSource, narrow span, human message, and hint. This aligns with the PR objective and integrates cleanly with miette reporters.


124-126: LGTM: Preserve “manifest parse error” context on deserialisation

Wrapping the deserialisation step with a stable context string keeps tests predictable while retaining diagnostic detail underneath.

tests/ast_tests.rs (1)

3-3: LGTM: Shift tests to miette::Result

Align the helper with the library’s diagnostic type to propagate structured errors through tests.

Also applies to: 9-11

tests/yaml_error_tests.rs (3)

1-5: Document the test module succinctly (LGTM).

Keep the inner doc comment. It satisfies the module-doc requirement and explains scope clearly.


11-13: Normalise ANSI escapes efficiently (LGTM).

Use of strip-ansi-escapes is appropriate and keeps assertions stable across environments.


38-44: Ignore the Diagnostic type-mismatch suggestion

The test’s err is a miette::Report (the error type alias from miette::Result) so err.as_ref() yields &Report, which implements Diagnostic. The existing call to render_report(&mut msg, err.as_ref()) compiles and works correctly. No change required.

Likely an incorrect or invalid review comment.

Cargo.toml (2)

14-14: Enable rich diagnostics (LGTM).

miette with the fancy feature is appropriate for graphical diagnostics and aligns with the tests.


65-76: Stabilise test output (LGTM).

strip-ansi-escapes as a dev-dependency is a sound choice for normalising rendered diagnostics in tests.

codescene-delta-analysis[bot]

This comment was marked as outdated.

codescene-delta-analysis[bot]

This comment was marked as outdated.

Copy link
Contributor

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

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

Actionable comments posted: 2

Caution

Some comments are outside the diff and can’t be posted inline due to platform limitations.

⚠️ Outside diff range comments (1)
src/manifest.rs (1)

271-296: DRY up repeated render-and-wrap patterns

Extract a tiny helper for the “render string with context + into_diagnostic + wrap_err” sequence to reduce repetition across rule/target/vars/string-or-list rendering.

Example helper (outside selected range):

fn render_str_with<E: minijinja::value::StructObject + Clone>(
    env: &Environment,
    tpl: &str,
    ctx: &E,
    what: &str,
) -> miette::Result<String> {
    env.render_str(tpl, ctx)
        .into_diagnostic()
        .wrap_err(what)
}

Then call with descriptive labels like "render rule command", "render target script", etc.

Also applies to: 297-319, 334-353

♻️ Duplicate comments (2)
src/manifest.rs (1)

17-35: Fix potential type mismatch in SourceSpan::new length parameter

Convert the computed length to the expected type to avoid API drift across miette versions and ensure spans render reliably.

     let len = end.saturating_sub(start);
-    SourceSpan::new(start.into(), len)
+    SourceSpan::new(start.into(), len.into())
tests/runner_tests.rs (1)

70-76: Assert against the error chain instead of only the immediate source

Avoid coupling to the immediate source() layer; assert over the full chain to remain resilient to future layering changes.

-    let err = run(&cli).expect_err("should have error");
-    assert!(err.to_string().contains("loading manifest at"));
-    let source = err.source().expect("source error").to_string();
-    assert!(
-        source.contains("manifest parse error"),
-        "expected parse error in source, got: {source}"
-    );
+    let err = run(&cli).expect_err("should have error");
+    assert!(err.to_string().contains("loading manifest at"));
+    let chain: Vec<String> = err.chain().map(|e| e.to_string()).collect();
+    assert!(
+        chain.iter().any(|s| s.contains("manifest parse error")),
+        "expected error chain to include 'manifest parse error', got: {chain:?}"
+    );
📜 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 eab64f1 and dbc4b1e.

📒 Files selected for processing (4)
  • src/manifest.rs (11 hunks)
  • src/runner.rs (5 hunks)
  • tests/runner_tests.rs (1 hunks)
  • tests/steps/ir_steps.rs (2 hunks)
🧰 Additional context used
📓 Path-based instructions (3)
**/*.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/ir_steps.rs
  • tests/runner_tests.rs
  • src/manifest.rs
  • src/runner.rs

⚙️ CodeRabbit Configuration File

**/*.rs: * Seek to keep the cyclomatic complexity of functions no more than 12.

  • Adhere to single responsibility and CQRS

  • Place function attributes after doc comments.

  • Do not use return in single-line functions.

  • Move conditionals with >2 branches into a predicate function.

  • Avoid unsafe unless absolutely necessary.

  • Every module must begin with a //! doc comment that explains the module's purpose and utility.

  • Comments and docs must follow en-GB-oxendict (-ize / -our) spelling and grammar

  • Lints must not be silenced except as a last resort.

    • #[allow] is forbidden.
    • Only narrowly scoped #[expect(lint, reason = "...")] is allowed.
    • No lint groups, no blanket or file-wide suppression.
    • Include FIXME: with link if a fix is expected.
  • 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/ir_steps.rs
  • tests/runner_tests.rs
  • src/manifest.rs
  • src/runner.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/ir_steps.rs
  • tests/runner_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
  • src/runner.rs
🧬 Code Graph Analysis (3)
tests/steps/ir_steps.rs (1)
src/ir.rs (1)
  • from_manifest (117-128)
tests/runner_tests.rs (1)
src/runner.rs (1)
  • run (116-137)
src/runner.rs (1)
src/ir.rs (1)
  • from_manifest (117-128)
🔍 Remote MCP Context7

Summary of additional facts useful for review

  • I attempted to resolve the added dependencies (miette, serde_spanned, strip-ansi-escapes) against the Context7 library index to confirm canonical crate IDs and metadata. The parallel lookup completed but did not return clear, authoritative entries for those three crates (search results were broad/unrelated). Recommend verifying the exact crate names/sources (crates.io / GitHub) and versions used in Cargo.toml before merging.

  • Practical notes for reviewers (from PR context + test changes):

    • Tests rely on strip-ansi-escapes to normalise GraphicalReportHandler output — ensure the dev-dependency name/version matches crates.io and compiles on CI.
    • serde_spanned is referenced in docs/design; confirm the crate exists and is the intended crate (some similarly named crates exist; verify API compatibility).
    • miette is a known diagnostics crate and is appropriate for the goal, but verify the Cargo.toml entry includes any required features and that code imports match the published API.

Tools used

  • Context7 resolve-library-id (parallel attempt to resolve miette, serde_spanned, strip-ansi-escapes) —
🔇 Additional comments (7)
src/manifest.rs (4)

37-50: Good: Internal diagnostic type with precise span and stable code

Keep YamlDiagnostic crate-visible and annotated with a stable diagnostic code and a static label. This aligns with the goal of structured, inspectable diagnostics.


52-82: Good: Narrow hints to indentation context; guard against noisy suggestions

The indentation probe constrained to leading whitespace mitigates false positives. Keep the hint strings short and actionable.


84-102: Good: Name the source and report line/column for actionable UX

Propagate the filename via NamedSource, compute a narrow SourceSpan, and include line/column in the message. This will render well with GraphicalReportHandler.


363-366: Good: Preserve IO context and thread source name into diagnostics

Wrap the read error with file path context and delegate to from_str_named with the actual filename to improve traceability.

src/runner.rs (3)

9-9: Good: Use miette’s Result, IntoDiagnostic, and Context consistently

Adopt miette end-to-end so caller-facing errors carry rich context and are renderable with handlers.


177-179: Good: Convert subprocess errors into diagnostics with context

Convert io::Result to a diagnostic and attach a clear “run ninja” context. This maintains the causal chain while aligning with the new diagnostics strategy.


257-264: Good: Preserve manifest parse diagnostics and add high-level context

Use with_context for the “loading manifest at …” banner and keep serialisation/IR generation errors wrapped with concise labels.

codescene-delta-analysis[bot]

This comment was marked as outdated.

codescene-delta-analysis[bot]

This comment was marked as outdated.

@leynos
Copy link
Owner Author

leynos commented Aug 19, 2025

@sourcery-ai review

Copy link
Contributor

@sourcery-ai sourcery-ai bot left a comment

Choose a reason for hiding this comment

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

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

  • Consider extracting the repeated .into_diagnostic().wrap_err* error‐wrapping pattern into a small helper or macro to cut down on boilerplate and ensure consistency.
  • The hint_for function’s branching logic is growing—consider refactoring it into smaller methods or a pattern‐matching table of error substrings to keep it maintainable as more hints are added.
  • Add a test for the scenario where a YamlError has no Location to verify that the default span and message fallback (line 1, column 1) behaves correctly.
Prompt for AI Agents
Please address the comments from this code review:
## Overall Comments
- Consider extracting the repeated `.into_diagnostic().wrap_err*` error‐wrapping pattern into a small helper or macro to cut down on boilerplate and ensure consistency.
- The `hint_for` function’s branching logic is growing—consider refactoring it into smaller methods or a pattern‐matching table of error substrings to keep it maintainable as more hints are added.
- Add a test for the scenario where a `YamlError` has no `Location` to verify that the default span and message fallback (line 1, column 1) behaves correctly.

## Individual Comments

### Comment 1
<location> `tests/yaml_error_tests.rs:38` </location>
<code_context>
+    "targets:\n  - name: 'unterminated\n",
+    &["YAML parse error", "line 2"],
+)]
+fn yaml_diagnostics_are_actionable(#[case] yaml: &str, #[case] needles: &[&str]) {
+    let err = manifest::from_str(yaml).expect_err("parse should fail");
+    let mut msg = String::new();
+    GraphicalReportHandler::new()
+        .render_report(&mut msg, err.as_ref())
+        .expect("render yaml error");
+    let msg = normalise_report(&msg);
+    for needle in needles {
+        assert!(msg.contains(needle), "missing: {needle}\nmessage: {msg}");
+    }
</code_context>

<issue_to_address>
Consider adding tests for empty YAML input and completely invalid YAML.

Tests currently focus on specific syntax errors but lack coverage for empty or malformed YAML inputs. Including these cases will improve error handling robustness.
</issue_to_address>

<suggested_fix>
<<<<<<< SEARCH
#[case(
    "targets:\n  - name: 'unterminated\n",
    &["YAML parse error", "line 2"],
)]
=======
#[case(
    "targets:\n  - name: 'unterminated\n",
    &["YAML parse error", "line 2"],
)]
#[case(
    "",
    &["YAML parse error", "empty", "no content"],
)]
#[case(
    "not: yaml: at all: %$#@!",
    &["YAML parse error", "invalid", "could not parse"],
)]
>>>>>>> REPLACE

</suggested_fix>

### Comment 2
<location> `src/manifest.rs:133` </location>
<code_context>

-    let manifest: NetsukeManifest = serde_yml::from_value(doc).context(ERR_MANIFEST_PARSE)?;
+    let manifest: NetsukeManifest = serde_yml::from_value(doc)
+        .into_diagnostic()
+        .wrap_err(ERR_MANIFEST_PARSE)?;

</code_context>

<issue_to_address>
Refactor repeated error conversion and context wrapping to avoid duplication.

There are multiple instances of .into_diagnostic().wrap_err() and similar error conversion patterns. Consider refactoring these into a helper or macro to keep the code DRY and maintain readability.
</issue_to_address>

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

@leynos leynos merged commit 938d147 into main Aug 19, 2025
4 checks passed
@leynos leynos deleted the codex/improve-yaml-error-message-clarity-wtfig1 branch August 19, 2025 19:34
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.

Add friendlier YAML syntax error messages

1 participant