Skip to content

fix(blend): store remembered checkout in state file - #23

Closed
frantic1048 wants to merge 1 commit into
masterfrom
codex/blend-state-file
Closed

fix(blend): store remembered checkout in state file#23
frantic1048 wants to merge 1 commit into
masterfrom
codex/blend-state-file

Conversation

@frantic1048

@frantic1048 frantic1048 commented Jun 11, 2026

Copy link
Copy Markdown
Owner

Summary

  • store the remembered blend checkout in state.json beside snapshots instead of generated config
  • keep legacy blend_dir config readable as a runtime fallback
  • add a stricter generated BlendOrder Nickel contract so orders/blend/order.ncl rejects stale config fields like blend_dir
  • simplify generated blend config so it only manages sandbox policy

Test

  • cargo fmt --check
  • cargo clippy -- -D warnings
  • cargo test

@sourcery-ai

sourcery-ai Bot commented Jun 11, 2026

Copy link
Copy Markdown

Reviewer's Guide

Stores the remembered blend checkout directory in a new JSON state file alongside snapshots, prefers this state over the legacy config when resolving blend_dir, and simplifies the generated Nickel starter config to only manage sandbox settings while keeping legacy config readable for backward compatibility.

Flow diagram for resolving blend_dir using state.json and legacy config

flowchart TD
    A[resolve_blend_dir] --> B{cli.blend_dir set?}
    B -- yes --> C[use cli.blend_dir]
    B -- no --> D{command is Init?}

    D -- yes --> E{find_blend_dir_from_current_dir}
    E -- found --> F[choice_from_current_dir]
    E -- not found --> G{find_blend_dir_from_state_or_config}

    D -- no --> H[find_blend_dir]

    H --> I{find_blend_dir_from_current_dir}
    I -- found --> F
    I -- not found --> G

    G --> J{state.read_blend_dir}
    J -- Some --> K[use blend_dir from state.json]
    J -- None --> L{find_blend_dir_from_config}
    L -- Some --> M[use blend_dir from config]
    L -- None --> N[bail: no blend_dir found]

    F --> O[BlendDirChoice with update_config_after_success]
    C --> P[BlendDirChoice from cli]
    K --> Q[BlendDirChoice from state]
    M --> R[BlendDirChoice from config]
Loading

File-Level Changes

Change Details Files
Persist remembered blend checkout directory in a JSON state file colocated with snapshots and expose APIs to read/write it.
  • Introduce a serializable StateFile struct with optional blend_dir field stored as a PathBuf.
  • Refactor StateStore::from_env into a test-only wrapper and add from_env_for_home(home) that respects XDG_STATE_HOME or falls back to home/.local/state.
  • Add StateStore::read_blend_dir, write_blend_dir, read_state_file, and state_file_path helpers to manage state.json with atomic writes and error context.
  • Add tests to validate from_env_for_home behavior and round-trip of blend_dir through the new state file.
blend/src/state.rs
Update Context and blend_dir resolution to use the new state file as primary source while keeping legacy config as a fallback, and stop writing blend_dir into config.toml in production.
  • Construct StateStore with from_env_for_home in Context::new and thread it into resolve_blend_dir and downstream helpers.
  • Change resolve_blend_dir/find_blend_dir/choice_from_current_dir to consult state.read_blend_dir first via find_blend_dir_from_state_or_config before falling back to TOML config.
  • Update Context::update_blend_dir wiring to write the blend_dir into state.json instead of config.toml and log the state file path when verbose.
  • Make BlendConfig.blend_dir optional and adjust write_blend_dir_config to only be used in tests while still preserving sandbox config.
  • Add find_blend_dir_from_state_or_config helper and adjust tests/expectations around using remembered state vs legacy config, including ensuring legacy config remains untouched when state is refreshed.
blend/src/context.rs
blend/tests/sync_e2e.rs
Simplify the generated Nickel starter file to only manage sandbox policy instead of embedding a concrete blend_dir path.
  • Replace starter_ncl(ctx) with a context-free starter_ncl() that no longer serializes ctx.blend_dir into the template.
  • Update write_blend_starter to call the new starter_ncl() and simplify the Nickel template: remove the ignore list and blend_dir from from_config, leaving only sandbox = "prefer".
  • Retain test support around writing starter files while aligning with the new simplified template.
blend/src/commands/init.rs
Modernize sorting logic for surgical Nickel AST rewrite helpers using sort_by_key with Reverse for clarity.
  • Replace manual descending sort_by closures on edit offsets with sort_by_key using std::cmp::Reverse for both surgical_rewrite and surgical_rewrite_with_structure.
  • Keep the semantics of applying edits from back to front to avoid offset shifting while simplifying the code.
blend/src/nickel/ast_utils.rs

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 1 issue, and left some high level feedback:

  • The atomic write logic for the state file (tmp path construction, write, rename, cleanup on failure) duplicates similar patterns elsewhere in StateStore; consider extracting a shared helper to reduce repetition and keep the behavior consistent.
  • The test from_env_for_home_falls_back_to_supplied_home manipulates XDG_STATE_HOME using unsafe and restores it only on the happy path, so a panic before the restore will leak environment changes into other tests; consider using a scoped env helper/guard to ensure restoration happens even on failure.
Prompt for AI Agents
Please address the comments from this code review:

## Overall Comments
- The atomic write logic for the state file (tmp path construction, write, rename, cleanup on failure) duplicates similar patterns elsewhere in `StateStore`; consider extracting a shared helper to reduce repetition and keep the behavior consistent.
- The test `from_env_for_home_falls_back_to_supplied_home` manipulates `XDG_STATE_HOME` using `unsafe` and restores it only on the happy path, so a panic before the restore will leak environment changes into other tests; consider using a scoped env helper/guard to ensure restoration happens even on failure.

## Individual Comments

### Comment 1
<location path="blend/src/state.rs" line_range="147" />
<code_context>
+        };
+        std::fs::write(&tmp, raw)
+            .with_context(|| format!("failed to write state temp file {}", tmp.display()))?;
+        let rename_result = std::fs::rename(&tmp, &path).with_context(|| {
+            format!(
+                "failed to rename state file {} -> {}",
</code_context>
<issue_to_address>
**issue (bug_risk):** Using `std::fs::rename` may fail on Windows if the target file already exists.

Because `std::fs::rename` errors on Windows when the destination already exists, repeated or concurrent calls to `write_blend_dir` may fail even though the operation is meant to be idempotent. To avoid this, consider explicitly removing the existing `path` before renaming (e.g. `std::fs::remove_file(&path)`), or introducing a platform-specific `replace_file` helper behind a `cfg` that provides atomic "replace" semantics across platforms.
</issue_to_address>

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

Comment thread blend/src/state.rs
};
std::fs::write(&tmp, raw)
.with_context(|| format!("failed to write state temp file {}", tmp.display()))?;
let rename_result = std::fs::rename(&tmp, &path).with_context(|| {

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

issue (bug_risk): Using std::fs::rename may fail on Windows if the target file already exists.

Because std::fs::rename errors on Windows when the destination already exists, repeated or concurrent calls to write_blend_dir may fail even though the operation is meant to be idempotent. To avoid this, consider explicitly removing the existing path before renaming (e.g. std::fs::remove_file(&path)), or introducing a platform-specific replace_file helper behind a cfg that provides atomic "replace" semantics across platforms.

@frantic1048
frantic1048 force-pushed the codex/blend-state-file branch from 9695db0 to ca7c69a Compare June 11, 2026 13:19
@frantic1048
frantic1048 force-pushed the codex/blend-state-file branch from ca7c69a to abcc8d2 Compare June 11, 2026 13:25
@houki-san houki-san Bot closed this Jun 11, 2026
@frantic1048
frantic1048 deleted the codex/blend-state-file branch June 22, 2026 13:12
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant