Skip to content

feat(blend): add check and format commands - #24

Closed
frantic1048 wants to merge 2 commits into
masterfrom
codex/blend-check-format
Closed

feat(blend): add check and format commands#24
frantic1048 wants to merge 2 commits into
masterfrom
codex/blend-check-format

Conversation

@frantic1048

@frantic1048 frantic1048 commented Jun 11, 2026

Copy link
Copy Markdown
Owner

Summary

  • add blend check to evaluate/typecheck selected or all order.ncl files through the embedded Nickel evaluator
  • validate order semantics in blend check, including missing active from_file sources
  • add blend format / blend fmt with --check and dry-run support using the in-process Nickel formatter
  • dogfood both commands in scoped blend CI under the default sandbox policy and commit the current Nickel formatter baseline for existing orders
  • split orders-only CI into a separate workflow that installs the latest blend release and runs blend check plus blend format --check
  • update docs and just check, and cover the new commands in E2E tests

Closes #22.

Verification

  • actionlint .github/workflows/blend-ci.yml .github/workflows/orders-ci.yml
  • cargo fmt -p blend --check
  • cargo test -p blend test_check_order
  • cargo test -p blend
  • HOME=/private/tmp/blend-ci-home-semantics cargo run -p blend -- check
  • HOME=/private/tmp/blend-ci-home-semantics cargo run -p blend -- format --check

@sourcery-ai

sourcery-ai Bot commented Jun 11, 2026

Copy link
Copy Markdown

Reviewer's Guide

Adds new Nickel-based blend check and blend format commands, integrates Nickel’s in-process formatter, introduces persisted blend-dir state in XDG state, tightens Nickel contracts and starter order for blend’s own config, and updates tests/docs and just targets accordingly.

Sequence diagram for new blend check and format commands

sequenceDiagram
    actor User
    participant Main as main_rs
    participant Ctx as Context
    participant CmdCheck as cmd_check
    participant CmdFormat as cmd_format
    participant Eval as NickelEvaluator
    participant Fmt as format_source

    User->>Main: blend check [orders]
    Main->>Ctx: Context::new(cli)
    Main->>CmdCheck: cmd_check(ctx, orders)
    CmdCheck->>Eval: NickelEvaluator::new(metadata)
    loop each selected order
        CmdCheck->>Eval: evaluate(order_ncl_path)
        Eval-->>CmdCheck: Result
    end
    CmdCheck-->>User: summary / exit status

    User->>Main: blend format [orders] [--check]
    Main->>Ctx: Context::new(cli)
    Main->>CmdFormat: cmd_format(ctx, orders, check)
    loop each selected order
        CmdFormat->>Fmt: format_source(source)
        Fmt-->>CmdFormat: formatted_source
        alt check or ctx.dry_run
            CmdFormat-->>CmdFormat: [no write]
        else write changes
            CmdFormat->>FileSystem: write(order_ncl_path, formatted_source)
        end
    end
    CmdFormat-->>User: summary / exit status
Loading

Flow diagram for resolving and persisting blend_dir state

flowchart TD
    A[Context::new] --> B[home_dir_from_cli]
    B --> C[StateStore::from_env_for_home]
    C --> D[resolve_blend_dir]

    D --> E[find_blend_dir_from_state_or_config]
    E --> F{state.read_blend_dir}
    F -->|Some path| G[use state blend_dir]
    F -->|None| H[find_blend_dir_from_config]
    H -->|Some path| I[use config blend_dir]
    H -->|None| J[bail: no blend_dir]

    subgraph On_successful_command
      K[Context::write_blend_dir_state]
      K --> L[state.write_blend_dir]
      L --> M[state.json under XDG_STATE_HOME]
    end

    G --> On_successful_command
    I --> On_successful_command
Loading

File-Level Changes

Change Details Files
Introduce Nickel-based blend check command to typecheck/evaluate order.ncl files.
  • Add Check variant to CLI with orders argument vector.
  • Implement cmd_check in new commands/check.rs using NickelEvaluator and discover_orders to typecheck/evaluate selected or all orders, with per-order success/error reporting and aggregate summary.
  • Wire cmd_check into main command dispatcher and export from commands module.
  • Update just check and CLAUDE.md to invoke blend check instead of blend view --dry-run.
  • Add E2E test verifying blend check succeeds on a fixture order and prints a success summary line.
blend/src/cli.rs
blend/src/commands.rs
blend/src/commands/check.rs
blend/src/main.rs
justfile
CLAUDE.md
blend/tests/sync_e2e.rs
Introduce Nickel-based blend format command to format or check formatting of order.ncl files via Nickel’s formatter.
  • Add Format (alias fmt) CLI variant with orders and --check flag.
  • Expose new format_source helper built on nickel_lang_core::format::format for in-process Nickel formatting.
  • Implement cmd_format in new commands/format.rs to format selected/all orders with dry-run and check-only modes, reporting changed files and summary counts.
  • Update nickel module exports to re-export format_source.
  • Add E2E tests covering successful format check, actual formatting and re-check of a modified order, and CLI output messages.
  • Add Nickel core dependency with format feature in Cargo.toml and bump tree-sitter to 0.26.
blend/src/cli.rs
blend/src/commands.rs
blend/src/commands/format.rs
blend/src/nickel/loader.rs
blend/src/nickel.rs
blend/Cargo.toml
Cargo.lock
blend/tests/sync_e2e.rs
Persist and use blend-dir state in XDG state instead of mutating config, while keeping legacy config backward-compatible.
  • Extend StateStore with a JSON StateFile (currently blend_dir) plus read/write helpers, including atomic write to state.json.
  • Adjust StateStore::from_env to delegate to new from_env_for_home, enabling tests to inject a synthetic HOME.
  • Add tests for new state behavior and environment fallback (from_env_for_home_falls_back_to_supplied_home, blend_dir_state_roundtrips_path).
  • Change Context::new to construct StateStore from home_dir and pass it into blend-dir resolution.
  • Replace config updates in Context::update_blend_dir with writes to state.json and updated logging message paths.
  • Allow BlendConfig.blend_dir to be optional (Option) to support legacy configs while new state lives in state.json.
  • Expose write_blend_dir_config only in tests and make it write optional blend_dir.
blend/src/state.rs
blend/src/context.rs
Update blend-dir resolution logic to prefer remembered state, keep legacy config untouched, and add tests around state vs config behavior.
  • Change resolve_blend_dir, find_blend_dir, and choice_from_current_dir to accept StateStore and to consult a new find_blend_dir_from_state_or_config helper.
  • Implement find_blend_dir_from_state_or_config to read blend_dir from state.json first, then fall back to config.toml.
  • Ensure find_blend_dir_from_config now respects optional blend_dir in config.
  • Adjust test_commands_use_configured_blend_dir_outside_checkout expectation string to reflect remembered state rather than config.
  • Revise test_valid_cwd_refreshes_stale_configured_blend_dir to assert that state.json is updated to the current checkout while legacy config retains the stale path.
  • Add E2E test test_blend_order_rejects_stale_blend_dir_config_field to validate that stale blend_dir fields in the blend order cause a Nickel contract error.
blend/src/context.rs
blend/tests/sync_e2e.rs
Tighten Nickel contracts and starter order for blend’s own configuration, removing blend_dir from starter and constraining config keys via contracts.
  • Extend generated Nickel contract (contract_ncl) and fixtures to define SandboxMode, BlendConfig, BlendFileEntry, and BlendOrder types, constraining sandbox values and limiting allowed config keys.
  • Update contract tests to assert presence of new contract symbols.
  • Refactor starter blend order (starter_ncl) to no longer inject blend_dir, to use SandboxMode/BlendOrder contracts, and to simplify ignore list and from_config content.
  • Add E2E test to ensure stale blend_dir field in blend order is rejected by Nickel contracts.
blend/src/nickel/generated.rs
blend/tests/fixtures/orders/order.contract.ncl
orders/order.contract.ncl
blend/src/commands/init.rs
blend/tests/sync_e2e.rs
Minor internal refactors and test additions to support new functionality and robustness.
  • Change AST edit sorting in surgical_rewrite and surgical_rewrite_with_structure to use sort_by_key with Reverse for clarity while preserving descending application order.
  • Document new blend check and blend format commands in NEW_BLEND.md and remove the prior roadmap item for a standalone validation command.
  • Add E2E tests for blend check, blend format, blend-dir state refresh, and stale config validation.
  • Update command help and CLAUDE.md documentation to mention new commands and adjusted behaviors.
blend/src/nickel/ast_utils.rs
NEW_BLEND.md
CLAUDE.md
blend/tests/sync_e2e.rs

Assessment against linked issues

Issue Objective Addressed Explanation
#22 Add a fmt (format) subcommand to the blend CLI that formats configuration/order Nickel files
#22 Expose and document the new formatting subcommand (including CLI wiring and tests to ensure it works)

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

@sourcery-ai sourcery-ai Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

Hey - I've found 2 issues, and left some high level feedback:

  • Both check and format define an identical selected_orders helper; consider extracting this into a shared function (e.g., in a small internal module) to avoid duplication and keep selection semantics in one place.
  • The temp-file-then-rename logic in StateStore::write and StateStore::write_blend_dir is nearly identical; factoring this into a shared helper would reduce repetition and make future changes to the write pattern less error-prone.
Prompt for AI Agents
Please address the comments from this code review:

## Overall Comments
- Both `check` and `format` define an identical `selected_orders` helper; consider extracting this into a shared function (e.g., in a small internal module) to avoid duplication and keep selection semantics in one place.
- The temp-file-then-rename logic in `StateStore::write` and `StateStore::write_blend_dir` is nearly identical; factoring this into a shared helper would reduce repetition and make future changes to the write pattern less error-prone.

## Individual Comments

### Comment 1
<location path="blend/src/commands/format.rs" line_range="8-15" />
<code_context>
+use crate::nickel::{NickelEvaluator, generated};
+use crate::output::log;
+
+fn selected_orders(ctx: &Context, orders: &[String]) -> Vec<String> {
+    let mut selected: Vec<String> = if orders.is_empty() {
+        discover_orders(&ctx.orders_dir).into_iter().collect()
+    } else {
+        orders.to_vec()
+    };
+    selected.sort();
+    selected
+}
+
</code_context>
<issue_to_address>
**suggestion:** Deduplicate `selected_orders` helper shared by `check` and `format` commands

This helper is identical to the one in `commands/check.rs`. To avoid duplication and keep order-selection behavior (sorting, defaults, etc.) consistent, please extract it into a shared helper (e.g., `commands::helpers` or a small shared module) and reuse it in both commands.

Suggested implementation:

```rust
use console::style;

use crate::context::Context;
use crate::nickel::{format_source, generated};
use crate::output::log;
use crate::commands::helpers::selected_orders;

```

1. Introduce a shared helper module, for example `blend/src/commands/helpers.rs`, with:
   ```rust
   use crate::compose::discover_orders;
   use crate::context::Context;

   pub fn selected_orders(ctx: &Context, orders: &[String]) -> Vec<String> {
       let mut selected: Vec<String> = if orders.is_empty() {
           discover_orders(&ctx.orders_dir).into_iter().collect()
       } else {
           orders.to_vec()
       };
       selected.sort();
       selected
   }
   ```
2. In `blend/src/commands/mod.rs` (or the relevant module file), `pub mod helpers;` so `crate::commands::helpers::selected_orders` resolves.
3. In `blend/src/commands/check.rs`, remove the local `selected_orders` definition and import/use `crate::commands::helpers::selected_orders` in the same way as `format.rs`.
4. Ensure any existing calls in `format.rs` and `check.rs` continue to call `selected_orders(ctx, orders)` without change; only the definition location and imports should move.
</issue_to_address>

### Comment 2
<location path="blend/tests/sync_e2e.rs" line_range="152-161" />
<code_context>
         }
     }

+    #[test]
+    fn from_env_for_home_falls_back_to_supplied_home() {
+        let prev_xdg = std::env::var_os("XDG_STATE_HOME");
</code_context>
<issue_to_address>
**suggestion (testing):** Strengthen `test_blend_order_rejects_stale_blend_dir_config_field` by asserting a non-zero exit status.

The test already verifies that a stale `blend_dir` surfaces a Nickel contract error on stdout/stderr. To also prove the CLI actually fails, please assert that the process exits with a non-success status, e.g.:

```rust
assert!(
    !output.status.success(),
    "blend view with stale blend_dir should fail\nstdout: {stdout}\nstderr: {stderr}",
);
```
This ensures contract violations affect the exit code, which is important for scripting and automation.
</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 on lines +8 to +15
fn selected_orders(ctx: &Context, orders: &[String]) -> Vec<String> {
let mut selected: Vec<String> = if orders.is_empty() {
discover_orders(&ctx.orders_dir).into_iter().collect()
} else {
orders.to_vec()
};
selected.sort();
selected

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

suggestion: Deduplicate selected_orders helper shared by check and format commands

This helper is identical to the one in commands/check.rs. To avoid duplication and keep order-selection behavior (sorting, defaults, etc.) consistent, please extract it into a shared helper (e.g., commands::helpers or a small shared module) and reuse it in both commands.

Suggested implementation:

use console::style;

use crate::context::Context;
use crate::nickel::{format_source, generated};
use crate::output::log;
use crate::commands::helpers::selected_orders;
  1. Introduce a shared helper module, for example blend/src/commands/helpers.rs, with:
    use crate::compose::discover_orders;
    use crate::context::Context;
    
    pub fn selected_orders(ctx: &Context, orders: &[String]) -> Vec<String> {
        let mut selected: Vec<String> = if orders.is_empty() {
            discover_orders(&ctx.orders_dir).into_iter().collect()
        } else {
            orders.to_vec()
        };
        selected.sort();
        selected
    }
  2. In blend/src/commands/mod.rs (or the relevant module file), pub mod helpers; so crate::commands::helpers::selected_orders resolves.
  3. In blend/src/commands/check.rs, remove the local selected_orders definition and import/use crate::commands::helpers::selected_orders in the same way as format.rs.
  4. Ensure any existing calls in format.rs and check.rs continue to call selected_orders(ctx, orders) without change; only the definition location and imports should move.

Comment thread blend/tests/sync_e2e.rs
Comment on lines +152 to +161
#[test]
fn test_check_order_success() {
let home = TempDir::new().unwrap();
let orders = fixtures_dir();

let output = run_blend(
home.path(),
&orders,
&["--sandbox", "never", "check", "toml-basic"],
);

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

suggestion (testing): Strengthen test_blend_order_rejects_stale_blend_dir_config_field by asserting a non-zero exit status.

The test already verifies that a stale blend_dir surfaces a Nickel contract error on stdout/stderr. To also prove the CLI actually fails, please assert that the process exits with a non-success status, e.g.:

assert!(
    !output.status.success(),
    "blend view with stale blend_dir should fail\nstdout: {stdout}\nstderr: {stderr}",
);

This ensures contract violations affect the exit code, which is important for scripting and automation.

@frantic1048
frantic1048 force-pushed the codex/blend-check-format branch 6 times, most recently from af89783 to f1926ee Compare June 11, 2026 16:33
@frantic1048 frantic1048 changed the title Add blend check and format commands feat(blend): add check and format commands Jun 11, 2026
@frantic1048
frantic1048 force-pushed the codex/blend-check-format branch from f1926ee to 381a27f Compare June 11, 2026 16:37
@frantic1048
frantic1048 force-pushed the codex/blend-check-format branch from 381a27f to 2fde8e9 Compare June 11, 2026 16:40
@frantic1048
frantic1048 deleted the codex/blend-check-format branch June 11, 2026 16:40
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

Support fmt subcommand for blend cli

1 participant