Skip to content

#133 + #134: the semantic flag stops loading the model; one context per posture - #138

Merged
samkeen merged 3 commits into
mainfrom
claude/gh-133-134-improvements-yzh7xc
Aug 3, 2026
Merged

#133 + #134: the semantic flag stops loading the model; one context per posture#138
samkeen merged 3 commits into
mainfrom
claude/gh-133-134-improvements-yzh7xc

Conversation

@samkeen

@samkeen samkeen commented Aug 3, 2026

Copy link
Copy Markdown
Contributor

Both improvements from the PR #135 survey, agreed with and implemented. Two commits, one per issue.

#133vault_info's "model-free read" becomes true

semantic_available called LocalEmbedder::load — parse config.json, build the tokenizer, mmap model.safetensors — on every vault_info, which is the first-paint path and every vault switch. The comment claiming otherwise was load-bearing and wrong.

Took option 1 from the issue: probe, don't load. EmbedConfig::is_model_provisioned is the repo's one "installed" check (files_present) and is literally load's own fail-fast precondition, so the flag answers the same question far cheaper. The accepted trade is stated where it is made: a present-but-corrupt model reads as semantic: true and fails at the first search/reindex instead — already fail-fast and actionable, and semantic's contract is "is a model installed", which is exactly what a file check answers. Options 2 (cache the load) and 3 (derive from embed_status) both buy less than they cost: the first adds staleness rules for a guarantee the flag doesn't promise, the second conflates "a model exists" with "this vault is embedded", which the UI already distinguishes via notes_embedded/notes_total.

Both stale comments now describe what happens, and a new b2-embed test pins the property the probe rests on: over an empty cache the probe and load agree (NotProvisioned); with the three required files present-but-corrupt the probe says installed while load fails with Load — so the file gate demonstrably passed and only the deeper parse failed.

#134 — two context structs, split by posture

Fifteen functions threaded the same values in three incompatible orders, with four #[allow(clippy::too_many_arguments)] holding the line. They now take ProjectionCtx (conn, root, idgen, cfg) or EmbedCtx (that plus the embedder), built by Vault::ctx / Vault::embed_ctx:

mv::move_note(self.embed_ctx(), &b2id, &old_rel, to)
rm::delete_dir(self.ctx(), dir)

The model-free rule the module docs state for rm / create_note / write is now the compiler's to keep — those ops hold no embedder and cannot embed. All four clippy allows and the ordering hazard are gone. Both structs are Copy, borrow-only, never stored — the sanctioned exception to "prefer owned fields" (CLAUDE.md, the NoteRow precedent), said so in their doc comments. ingest_vault deliberately keeps its loose arguments: it exists to supply the default ChunkConfig, so it builds the context itself and its ~15 test call sites are untouched.

Riders, same files and same review:

  • the move preflight (same-path refusal, occupied-target refusal with the case-only-rename carve-out, create_dir_all + rename) → refuse_same_path / refuse_occupied / rename_with_parents. Composed rather than folded into one preflight on purpose: a folder move refuses an inside-itself destination and a missing source before it looks at the target, and that error precedence is part of each op's contract.
  • the "rewrite the inbound files" loop, written three times → rewrite_inbound, which is also the single home of move_dir's let empty = BTreeMap::new() sentinel.
  • the wikilink .md-convention replacement and the fragment-preserving resource replacement → wiki_replacement / resource_replacement, with direct unit tests including the #fragment rule, which had none.

Verification

cargo fmt --check, cargo clippy --workspace --exclude b2-desktop --all-targets (clean), cargo test --workspace --exclude b2-desktop (all green, 31 binaries), and the ui/ suite (27 tests).

b2-desktop was not compiled here — this container is Linux and the crate's WebView deps need gdk-3.0/webkit, so cargo check -p b2-desktop dies in gdk-sys's build script (CI runs macOS, where just ci covers it). The #133 diff there is three lines plus comments; its changed expression was type-checked against the real b2-embed API in a scratch crate, and the identical is_model_provisioned(&config.model) call runs in the new b2-embed test. #134 does not touch b2-desktop — the host only ever calls Vault.

One observation, deliberately not fixed

Extracting the replacement rules made it visible that move_note repairs only the [[…]] form, so an inbound Markdown-form link to a note ([text](concepts/memory.md), which resolve_target does resolve to a note) is left stale by a note move. That is pre-existing behavior, not something #134 asked to change, and fixing it properly needs the note move to learn the relative-vs-root convention logic the resource move has — worth its own issue if you want it.


Generated by Claude Code

Summary by CodeRabbit

  • Bug Fixes

    • Improved note, resource, and directory moves to preserve relative links, aliases, resource fragments, and authored Markdown paths.
    • Corrected semantic-search availability checks so they accurately reflect whether the required model is provisioned.
    • Preserved indexing behavior for cancellations, collisions, incremental updates, and progress reporting.
  • Documentation

    • Clarified how embedding coverage and semantic-search availability are reported.

claude added 2 commits August 3, 2026 18:10
Closes #133. vault_info documented itself as a model-free read while
semantic_available called LocalEmbedder::load — parsing config.json,
building the tokenizer, and mmapping model.safetensors — on every
call, and vault_info sits on the first-paint path and on every vault
switch, exactly the path the project/embed split exists to keep
model-free.

semantic_available now asks EmbedConfig::is_model_provisioned, the
repo's one "installed" check (files_present) and literally load's own
fail-fast precondition, so the flag answers the same question without
the load. What that trades is stated where the trade is made: a
present-but-corrupt model reads as semantic: true here and fails at
the first search/reindex instead — already fail-fast and actionable,
and the flag's contract is "is a model installed", which is what a
file check answers. The two comments that said otherwise now say what
happens.

A new b2-embed test pins the property the probe rests on: over an
empty cache the probe and load agree (NotProvisioned), and with the
three required files present-but-corrupt the probe says installed
while load fails with Load — so the file gate demonstrably passed and
only the deeper parse failed.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01NVJSUVP6FHdhYoAPfqrcTU
…er) bundle

Closes #134. Behavior-preserving. Fifteen b2-core functions threaded
the same four-or-five values in three incompatible parameter orders,
with four #[allow(clippy::too_many_arguments)] holding the line;
rm::reproject_dangled used a fourth order inside its own file.

They now take one of two view structs, split along the model-free vs.
embedding posture the module docs already stated: ProjectionCtx
(conn, root, idgen, cfg) and EmbedCtx (that plus the embedder). The
posture becomes the type system's to keep — rm / create_note / write
hold no embedder and so cannot embed — and the ordering hazard and
all four clippy allows are gone. Vault::ctx / Vault::embed_ctx build
them, so a façade call is now e.g. mv::move_note(self.embed_ctx(),
&b2id, &old_rel, to). Both are Copy, borrow-only, never stored: the
sanctioned exception to "prefer owned fields" (CLAUDE.md; the NoteRow
precedent), said so in their doc comments. ingest_vault keeps its
loose arguments deliberately — it exists to supply the default
ChunkConfig, so it builds the context itself.

The riders from the same survey of mv.rs, all in this diff because
they are the same files and the same review:

- the move preflight (same-path refusal, occupied-target refusal with
  the case-only-rename carve-out, create_dir_all + rename), written
  three times, is now refuse_same_path / refuse_occupied /
  rename_with_parents. Composed rather than folded into one preflight
  on purpose: a folder move refuses an inside-itself destination and
  a missing source *before* it looks at the target, and that error
  precedence is part of each op's contract.
- the "rewrite the inbound files" loop, written three times, is now
  rewrite_inbound — which is also the one home of move_dir's
  `let empty = BTreeMap::new()` sentinel.
- the wikilink .md-convention replacement and the fragment-preserving
  resource replacement, each computed in two places, are now
  wiki_replacement and resource_replacement. As pure functions they
  get direct unit tests, including the #fragment rule, which had none
  — its only cover was a whole-vault move.

The ~12 integration-test call sites build their contexts with the new
constructors; the ~15 ingest_vault sites are untouched.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01NVJSUVP6FHdhYoAPfqrcTU
@coderabbitai

coderabbitai Bot commented Aug 3, 2026

Copy link
Copy Markdown

Review Change Stack

Warning

Review limit reached

@samkeen, you've reached your PR review limit, so we couldn't start this review.

Next review available in: 44 minutes

You've used all free OSS reviews for now. Wait for the free limit to reset to keep reviewing this public repository.

How can I continue?

After more reviews become available, a review can be triggered using the @coderabbitai review command as a PR comment. Alternatively, push new commits to this PR.

To avoid repeated limits, reduce automatic review volume by pausing incremental auto-reviews earlier, using label-based review opt-in, excluding WIP or generated PR titles, or requesting reviews manually when the PR is ready. If your team needs uninterrupted high-volume reviews, an organization admin can enable usage-based reviews.

How do review limits work?

CodeRabbit enforces per-developer PR review limits for each organization. Most developers receive the normal plan review availability.

For paid Pro and Pro+ PR reviews, CodeRabbit uses adaptive limits for sustained high-volume activity. When a developer's recent PR review activity reaches the 95th percentile or higher among CodeRabbit users, additional reviews become available more gradually as earlier reviews age out of the rolling window.

Please refer docs for additional details.

Review details
⚙️ Run configuration

Configuration used: Organization UI

Review profile: CHILL

Plan: Pro Plus

Run ID: cf509a26-afef-472c-a4c6-7eec78cbabce

📥 Commits

Reviewing files that changed from the base of the PR and between 473dd70 and 6a2095d.

📒 Files selected for processing (1)
  • crates/b2-core/src/mv.rs
📝 Walkthrough

Walkthrough

The PR introduces ProjectionCtx and EmbedCtx for core projection and embedding operations. It updates ingestion, vault, add, delete, and move APIs, migrates tests, and changes desktop semantic availability to use a lightweight model provisioning check.

Changes

Core context and vault operation refactor

Layer / File(s) Summary
Projection and embedding contexts
crates/b2-core/src/ingest.rs
ProjectionCtx and EmbedCtx bundle projection and embedding dependencies. Ingestion and projection functions use these contexts.
Context-based move operations
crates/b2-core/src/mv.rs
Note, resource, and directory moves use shared validation, rewriting, renaming, and context-based re-ingestion flows. Tests cover link conventions and fragments.
Vault, add, and delete integration
crates/b2-core/src/add.rs, crates/b2-core/src/rm.rs, crates/b2-core/src/vault.rs
Vault helpers construct shared contexts. Add and delete operations pass contexts to projection and embedding APIs.
Core API migration tests
crates/b2-core/tests/*
Ingestion, collision, graph, progress, projection, and embedding tests construct and reuse the new contexts.

Model availability probe

Layer / File(s) Summary
Provisioning-only semantic availability
crates/b2-desktop/src/main.rs, crates/b2-desktop/src/commands.rs, crates/b2-embed/src/model.rs
Semantic availability checks model provisioning without loading weights. Tests distinguish empty caches from corrupt provisioned caches.

Estimated code review effort: 4 (Complex) | ~45 minutes

Possibly related PRs

Suggested reviewers: claude

Poem

A rabbit bundles roots and IDs,
With embeddings tucked inside.
Notes hop through projection paths,
Links keep fragments where they ride.
Models check their caches first—
Then this little PR can hide.

🚥 Pre-merge checks | ✅ 5
✅ Passed checks (5 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title check ✅ Passed The title clearly summarizes both primary changes: model provisioning checks for semantic availability and separate projection and embedding contexts.
Docstring Coverage ✅ Passed Docstring coverage is 100.00% which is sufficient. The required threshold is 80.00%.
Linked Issues check ✅ Passed Check skipped because no linked issues were found for this pull request.
Out of Scope Changes check ✅ Passed Check skipped because no linked issues were found for this pull request.
✨ Finishing Touches
📝 Generate docstrings
  • Create stacked PR
  • Commit on current branch
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Commit unit tests in branch claude/gh-133-134-improvements-yzh7xc

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

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

@coderabbitai coderabbitai 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.

🧹 Nitpick comments (1)
crates/b2-core/src/mv.rs (1)

261-278: 📐 Maintainability & Code Quality | 🔵 Trivial | 💤 Low value

Consider BTreeSet<&str> for the touched-file key set.

touched collects &String values. &str keys work for both BTreeMap lookups and vault_root.join(...), and match the project preference for &str over &String. This is a local readability change only; behavior is identical.

♻️ Optional simplification
-    let touched: BTreeSet<&String> = wiki.keys().chain(md.keys()).collect();
+    let touched: BTreeSet<&str> = wiki.keys().chain(md.keys()).map(String::as_str).collect();
     for src_path in touched {
         let abs = vault_root.join(src_path);
         let raw = fs::read_to_string(&abs)?;
         let (pass1, n1) = rewrite_links(&raw, wiki.get(src_path).unwrap_or(&none));
         let (pass2, n2) = rewrite_md_targets(&pass1, md.get(src_path).unwrap_or(&none));
         if n1 + n2 > 0 {
             fs::write(&abs, pass2)?;
-            rewrote.push(src_path.clone());
+            rewrote.push(src_path.to_string());
             links_rewritten += n1 + n2;
         }
     }

As per coding guidelines: "Prefer &str over &String, &[T] over &Vec<T>".

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

In `@crates/b2-core/src/mv.rs` around lines 261 - 278, Update the touched-file key
set in rewrite_inbound to use BTreeSet<&str> instead of BTreeSet<&String>,
converting the chained wiki and md keys to string slices while preserving the
existing lookups, path joining, and rewrite behavior.

Source: Coding guidelines

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

Nitpick comments:
In `@crates/b2-core/src/mv.rs`:
- Around line 261-278: Update the touched-file key set in rewrite_inbound to use
BTreeSet<&str> instead of BTreeSet<&String>, converting the chained wiki and md
keys to string slices while preserving the existing lookups, path joining, and
rewrite behavior.

ℹ️ Review info
⚙️ Run configuration

Configuration used: Organization UI

Review profile: CHILL

Plan: Pro Plus

Run ID: 2a7b0dce-3a09-4978-99a9-f9e1a097b77f

📥 Commits

Reviewing files that changed from the base of the PR and between a503150 and 473dd70.

📒 Files selected for processing (13)
  • crates/b2-core/src/add.rs
  • crates/b2-core/src/ingest.rs
  • crates/b2-core/src/mv.rs
  • crates/b2-core/src/rm.rs
  • crates/b2-core/src/vault.rs
  • crates/b2-core/tests/cancel.rs
  • crates/b2-core/tests/collision.rs
  • crates/b2-core/tests/embed.rs
  • crates/b2-core/tests/graph.rs
  • crates/b2-core/tests/project_embed.rs
  • crates/b2-desktop/src/commands.rs
  • crates/b2-desktop/src/main.rs
  • crates/b2-embed/src/model.rs

Review feedback on PR #138. The set of files to rewrite collected
&String, where the repo's own rule is &str over &String — and nothing
here wanted the owned type: BTreeMap::get takes a Borrow key and
Path::join takes an AsRef, so both call sites accept the slice
unchanged. Behavior identical.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01NVJSUVP6FHdhYoAPfqrcTU
@samkeen
samkeen merged commit 04ff2be into main Aug 3, 2026
2 checks passed
@samkeen
samkeen deleted the claude/gh-133-134-improvements-yzh7xc branch August 3, 2026 18:38
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.

2 participants