Repository navigation
Refactor CLI into modular command structure - #246
Conversation
Tooltips, menu chords and dialog hints spelled the shipped chords by hand (⌘E, ⌘[, ⌘], ⇧⌘F, ⌘,, ⌘N, ⇧⌘N, F2, ⇧F10, ⌘⌫, ⌘⏎, ⌃Tab), so they went stale the moment a user rebound one. They now come out of displayKeys, and the shell's static hints live in hints.ts and are repainted by setOverrides (the shell and the live editor's bar are the two surfaces render() doesn't rebuild, so #open-chat's title used to stay on the boot-time chord). hints.test.ts pins the hints under a rebind and walks every UI string literal through TypeScript's parser, failing on a chord spelled by hand. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01GCwoP7o76vxQ8rEQq7zM8w
- api.ts: the ping and neighbors wrappers (nothing calls them; the host commands are going too). - bindings.ts: chordsOverlap, unused since menukeys.ts compares keystrokes. - keymap.ts: currentKeys, never called. - recorder.ts: reservedChords and its test. Its doc said the recorder names the menu's chords up front, but recorderHtml never rendered them. - style.css: .modal-wide and its stale anomaly-panel comment. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01GCwoP7o76vxQ8rEQq7zM8w
- beginIndexRun/endIndexRun for the three index runs' meter lifecycle, and refreshOpenNoteAfterIndex for the after-run re-read + discovery refresh (autoIndexOnOpen still skips it entirely while an editor is open). - enterDocument: the core loadNote and loadResource share. - clearDiscovery/clearConnections/adoptExplain, adoptResource, adoptCoverage (a VaultInfo's embedding coverage). - Chat: applyChatConfig is now really the shared tail of Save, the model picker and Remove key; adoptChatSetup; openChat; chatReady/chatStateOf in chat.ts replace three inline chatEmptyState(...) === "ready" reads; and LOCAL_CHAT_ENDPOINT moves to chat.ts, where the Endpoint placeholder uses it. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01GCwoP7o76vxQ8rEQq7zM8w
- The contextmenu handler, the drag start and the drop target rebuilt a
tree row's node ref by hand; they use treeRowRef (and a TREE_ROW selector)
now, with the folder a row stands for from move.ts's folderContext.
- openDocPath (state.ts) for the pane's document path.
- revealDir replaces expandAncestors (a re-implementation of dirChain) and
the five hand-written dirChain loops.
- isWithin (move.ts) for "this folder or under it", written four ways before;
moveRefusal says why a destination is refused, canMoveInto is defined
from it, and the Move… modal reads the reason instead of re-deriving it.
- render.ts uses baseName instead of split("/").pop().
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01GCwoP7o76vxQ8rEQq7zM8w
The search caveat, the Similar empty state, the graph's ghost hint, Settings → Index and chat's retrieval note each re-derived the embedding tiers from the three raw numbers. coverage.ts reads them once — the model as its own fact, the embedded fraction as a tier — and each surface keeps its own copy and its own order of asking. Every surface's output was snapshotted across a state matrix before and after (224 cases, identical); coverage.test.ts pins the classifier and each surface's copy per tier. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01GCwoP7o76vxQ8rEQq7zM8w
…etup sentences b2-llm and b2-embed now answer the questions both adapters were each answering for themselves: - `fake_requested()` + `ENV_LLM`/`ENV_EMBEDDER`, and b2-llm's `FAKE_NOTICE`, so B2_LLM=fake / B2_EMBEDDER=fake mean one thing. - `b2_llm::provider` / `probed_provider` and `b2_embed::embedder_for`: the one wiring rule (fake when forced, else the real thing) in one place. - `unreachable_message` / `model_missing_message` are public (the unused detail parameter dropped), and the Ollama sentence uses `pull_command`. The non-Ollama model-missing sentence now says "Load it there." Smaller clean-ups on the way through: - sse: EOF is dispatched as a final blank line instead of a copy of that branch; a tool call under assembly is a named `PartialCall`. - One `is_valid_tool_call_cap` predicate behind both cap checks. - setup: host_of/origin_of share `scheme_and_authority`; `installed_models` returns Option; `setup` is a private module and `is_local`, `tier_for_ram`, `system_ram_gb` are private; the unused `OpenAiCompatProvider::config()` is gone. - b2-embed: HF_ENDPOINT is passed into `from_raw` (testable, pinned); the Metal fallback reports via tracing (b2::embed) instead of eprintln; single and batch embedding share one forward/CLS/normalize step (the single path still encodes one unpadded sequence); one cache-path join; `find_model`, `model_dir`, `sanitize`, `default_cache_dir` internal. `LocalEmbedder::configured_model()` exposes the untagged repo id. - quickstart documents source/cache_dir/query_prefix and HF_ENDPOINT. - wire.rs: drop an unused `mut` clippy now flags. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01GCwoP7o76vxQ8rEQq7zM8w
vault.rs had grown to 2,300 lines holding three modules' worth of code. The view types move to vault/views.rs (re-exported, so b2_core::vault::X paths are unchanged), ask + the Why? tool turn to vault/chat.rs, and the one-line excerpt helpers to snippet.rs. An orphaned doc comment that rustdoc was attaching to ToolDesk goes back onto snippet(). While moving, the façade stops repeating itself: - write and write_frontmatter share one save_guarded (resolve, revision guard, splice, write, re-project, read the revision back). - One require_resource check and one refuse_resource_anchor guard replace five hand-written copies. - A search hit resolves in one chunks JOIN notes read (db::chunk_hit) instead of three or four, so the note and chunk come from one snapshot. - A neighbor's title and created date are one read (db::note_header). - neighbor_paths / shared_neighbors / note_summary replace the neighbor sets and NoteSummary builds written out by hand in why_similar and explain_similar; ToolDesk no longer copies the pair WhyFacts carries. - query_snippet windows on search::query_terms, the tokenizer the lexical half ranked with, instead of a second one. - AddReport and ReindexProgress are re-exported from vault like every other report. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01GCwoP7o76vxQ8rEQq7zM8w
…+ editor commands out of main.ts - wireEvents was one 1,350-line function; it is now a list of named wiring functions (focus memory, clicks, context menu, tree keys, side keys, find bar, forms, chords, mouse history, window blur, drags) called in the old registration order. Settings' click branch is settingsClick. - The context-menu click branch's thirteen near-identical blocks are three tables of MenuItem (attr, closeFirst, act) and one contextMenuClick. - findfield.ts: the editor find StateField, its effect and decorations. - editorcmds.ts: format, list shift, table, rich paste and [[ completion, each split into a pure plan over an EditorState and a run… that dispatches it. Both modules have EditorState tests. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01GCwoP7o76vxQ8rEQq7zM8w
A pure move: no line of behaviour changes. main.rs keeps the module doc,
`main`, `dispatch` and `print_json`; the rest goes where its domain is:
- args.rs Cli, Command, LlmArgs, AGENT_HELP, the vault-root rules
- logging.rs init_logging
- wiring.rs open_vault / open_llm and the fake switches
- error.rs CliError, user_message and its unit tests
- reindex.rs init, reindex, status, the lock and --cancel
- chat.rs ask, why, chat and the streamed rendering
- read.rs neighbors, explain, search, similar (+ --explain), the
presentation helpers and the search_report tests
- write.rs add, write, mv, rm, link and their tallies
- cancel.rs the CANCEL/ANSWERING flags and cancel_flow, which both
reindex.rs and chat.rs read
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01GCwoP7o76vxQ8rEQq7zM8w
The four harness examples each carried their own copy of the same plumbing. They now share one module, `crates/b2-embed/examples/common/mod.rs` (Cargo discovers only `examples/*.rs` and `examples/*/main.rs`, so it is a module, not an example). `groundedness` reuses it from b2-llm by `#[path]`; the module imports nothing b2-llm's dev-dependencies don't also carry. Shared: `truncate` (the saturating form, so `max == 0` no longer underflows in eval/calibrate/stability), `git_short_sha`, `append_result`, `cosine_of`, `pile_stats`, the strength-band landmarks with one `Band` classification, the harness's z restatement (`passage_z`; calibrate applies its replayed MIN_POPULATION floor first, as before), the flat-corpus throwaway vault (`ScratchVault`), the title-as-query probe, and stability's `reject_unknown_flags`/`has_flag`. The flag check also refuses a value on a switch (`--verbose=1` passed the name check and then matched no `has_flag`, silently running a different measurement — the case the check exists for). `calibrate --search` now reads term coverage off the engine's `QueryTermView::idf` instead of re-deriving the IDF by hand (same formula, same inputs). The deliberate restatements stay restatements: eval's labelled bake-off still recomputes coverage itself as the drift check, and the z recheck is still the harness's own arithmetic, never an engine call. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01GCwoP7o76vxQ8rEQq7zM8w
`eval` read its flags with `args().any(|a| a == "--sweep")`, so a typo (`--swep`, `--stemmer=1`) ran the default measurement alone and printed a report the reader took for the A/B they asked for. It now goes through the shared `reject_unknown_flags` (the check `stability` already had) and exits 1 on anything but `--sweep`/`--stemmer`. A bare `--` is still dropped, as `stability` does, so a pasted `cargo run … -- --sweep` keeps working; every invocation in the Makefile and docs/evals.md is unchanged. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01GCwoP7o76vxQ8rEQq7zM8w
render.ts (2,687 lines) keeps the tree, note pane, discovery column, menus and dialogs, and re-exports everything the app and the suite imported from it. The rest moves to modules named for their surface: - markdown.ts: renderMarkdown and the marked config — the one sanitizing Markdown path, now importable without the panes (livepreview.ts does). - chatview.ts, graphview.ts, explainview.ts, keysview.ts, settingsview.ts. - widgets.ts: markup more than one surface paints — sideTab, strengthHtml, segmentedHtml (theme and chat-mode controls), reindexMeterHtml (the top bar's meter and Settings → Index's). Also keysGroupsHtml (the reference and the ⌘ sheet), modelDetail in chat.ts (the setup card and the model picker), and tabDomId exported from settingstabs.ts for main.ts's two hand-built settings-tab ids. The module graph is acyclic. Every exported surface was snapshotted across a state matrix before and after the move (755 cases, identical). Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01GCwoP7o76vxQ8rEQq7zM8w
…ly, render once
Wiring: open_vault/open_llm shrink to b2_embed::embedder_for and
b2_llm::probed_provider over the CLI's flags; the fake switches and the
fake-chat notice are the libraries' (the notice now prints as b2-llm's
sentence, without the "note:" prefix).
Errors:
- Unreachable / model-missing use b2-llm's sentences and append only the
CLI's flag hint (--llm-url, --llm-model with the served list).
- The InvalidRelation verb list is read from b2_core::relation::CORE.
- The `_ =>` catch-all is gone: EmbedError::Load/Config/Io/UnknownModel
get their own messages (Config names the config.toml path), so
`b2 init` no longer says "check the vault path"; the internal
remainder is spelled out so a new CliError variant fails to compile.
- is_ollama is no longer re-exported by b2-llm (no caller left).
Rendering:
- `emit(json, &value, human)` replaces the per-command
`if cli.json { print_json } else { … }` blocks; explain and
similar --explain get named human printers.
- `install_cancel_on_sigint(exit_when_idle)` is the one Ctrl-C handler
(the REPL passes true), and `while_answering` owns the per-turn
CANCEL/ANSWERING bookkeeping, so the flags are private to cancel.rs.
- `status --json` and `reindex --cancel --json` serialize typed
StatusView/CancelView structs with the same keys.
Comments: the orphan is_ollama block, cmd_mv's doubled comment, the
VaultRequired doc (every write command, not just reindex) and the
write-command list in Cli.vault are fixed.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01GCwoP7o76vxQ8rEQq7zM8w
…kernel and search's fusion tail
Ingest (flow ①):
- ingest_file / project_file / ingest_vault return () — every caller
discarded Ingested / Projected. plan_reindex returns two counts, not a
per-note Vec. ingest_vault_with_progress and IngestOutcome go:
Vault::reindex composes project_vault + embed_vault itself.
- project_note_and_chunks takes a Rechunk enum instead of a (force,
consult_vectors) bool pair whose fourth combination never occurred.
- One walk_vault (walk + sort), one body_hash shared by the projection
and the dry-run forecast, one skipped() constructor.
Discovery: stage 2 and passage_pairs ran two hand-copied max-sim loops
that had to agree for the card and its explanation to agree. They now
share nearest_pairs, so they agree by construction. Population's unread
n field goes and the type is private.
Search: keyword-only and hybrid share one fuse() tail and
Retrieval::empty(). Float sorts use total_cmp (same order for every
reachable value, a defined place for NaN).
db: embedding_space_exists is table_exists; embedding_space_matches reads
recorded_embedder instead of re-parsing meta. Error::Serde was never
constructed. Missing Debug derives added; docs citing retired plans
("slice 1", "research §9b", migrate()) corrected.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01GCwoP7o76vxQ8rEQq7zM8w
link.rs exposes link_spans, a per-line span scanner that parse_links builds on, and a move now rewrites targets from those spans instead of its own whole-file scan. A stray [[ on one line no longer pairs with a ]] on a later one, so a link ingest projected is a link a move repairs. ParsedLink loses alias and typed (read only by link.rs's own tests); render_relation is the inverse of parse_relation. move_note, move_resource and move_dir now only validate and build a MoveSet; one executor plans the rewrites, commits them with the rename, re-keys every member and re-projects every inbound source. Behaviour made consistent: a self-link is reported under the note's new path, a resource move re-projects every inbound source (not only the rewritten ones), and a wikilink keeps its #heading fragment. A resource can no longer be moved to a .md path (MoveDestination), which a rebuild would have indexed as a note. A resource row missing from the index is inventoried at its new path instead of failing after the rename. delete_note, delete_resource and delete_dir run one delete pipeline over db::inbound_sources. db gains delete_note_row, delete_resource_row and repoint_resource (moved from mv), and the note/resource twin helpers share private implementations. pathspec gains the vault-relative path algebra (file_name, parent_dir, extension, is_md, is_under, rebase, relativize), and normalize_rel_md no longer turns Foo.MD into Foo.MD.md. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01GCwoP7o76vxQ8rEQq7zM8w
NoteFields.title (read only by tests: a note's title is its filename and the frontmatter title: key is inert, round-tripped with the raw bytes) and CoreVerb::symmetric / relation::is_symmetric (unused; contradicts being its own inverse stays pinned by a test on the inverse labels). display_title reads the file name and extension through pathspec. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01GCwoP7o76vxQ8rEQq7zM8w
add checked abs.exists() and then wrote, so a file appearing in between was clobbered. It now goes through import's create-new placement (import::place_new), where 'does it exist' and 'claim it' are one syscall and AlreadyExists is the op's own target-exists error. The refusal now also covers a folder at the path. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01GCwoP7o76vxQ8rEQq7zM8w
…rrent docs ContextPassage::block is the one passage layout; ChatRequest::system_message and chat::passage_block (kept for the façade) both render through it, byte-identical. FakeEmbedder::new clamps a zero dimension to 1 instead of asserting on a production path; unpack_f32 goes through unpack_f32_into. chunk.rs cites docs/index-engine.md §1 instead of a deleted chunking spec's D1-D5 (which collided with invariants.md's D ids) and names its levers in plain words; scan and protected_regions share one fence-line rule. Stale docs fixed in embed.rs and graph.rs. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01GCwoP7o76vxQ8rEQq7zM8w
… storage move out - leaveEdits (the drawer guard + close-the-editor pair before a navigation), teardownEditor, bufferUnsaved, refusedWhileIndexing, confirmDelete (the delete confirm's button and ⏎), searchInput, and findByData behind treeRowEl/sideRowEl/gnodeEl. - executeMove calls one mover chosen by kind (all three report alike); executeDelete shares the note and resource branch. - theme.ts owns the appearance preference's values and storage, and embedreminder.ts the banner's opt-out, the way zoom.ts, panes.ts and keymap.ts own theirs. Both are tested with and without storage. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01GCwoP7o76vxQ8rEQq7zM8w
state.ts: contextMenu covers the file tree as well as a discovery card, current is the centre pane's note (not a "left pane"), and Settings is a full-window surface rather than a modal box. api.ts: menuChords is read once to check menukeys.ts's mirror; the keyboard reference never listed from it. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01GCwoP7o76vxQ8rEQq7zM8w
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01GCwoP7o76vxQ8rEQq7zM8w
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01GCwoP7o76vxQ8rEQq7zM8w
…red wiring - `Slot` (slot.rs) replaces the two hand-rolled state machines (reindex_running/reindex_cancel and ask_running/ask_cancel, ReindexGuard and AskGuard, claim-then-arm at each call site): `try_claim` returns a guard that releases on drop and clears a stale cancel; `cancel`, `cancelled`, `flow`, `cancel_and_wait`. Tests re-pointed; the two pure state-machine tests move to slot.rs. - `read_op` / `semantic_op` make every command body one line; the `*_impl` fns the tests call stay, as one-liners over them. - Wiring goes through b2_embed::embedder_for, b2_embed::fake_requested, b2_llm::provider and b2_llm::fake_requested. open_vault returns the id of the model it actually loaded, so `embed` no longer re-reads the config (which could name a different model) to attribute its time. - Dead IPC removed: the `ping` and `neighbors` commands (only ui/src/api.ts wrappers referenced them) and the ping test. - `open_resource` reads the root once, so the inventory check and the path handed to the OS belong to the same vault. - stats returns the IPC row itself (`EmbedStat`, ModelStat flattened — same model/total_ms/chunks/runs keys, pinned by a test) instead of a mirrored struct plus a hand-written From. - state_file.rs: one path + best-effort-write helper behind last-vault, chat.json and embed-stats.json. - The app failing to start logs and exits 1 instead of `.expect()`. - error.rs: the relation verbs come from b2_core::relation::CORE, and a broken embedder config names config.toml instead of "check the vault". - logging.rs: the implied-filter comment no longer claims the CLI differs. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01GCwoP7o76vxQ8rEQq7zM8w
eval.rs had grown to ~4,500 lines. It is now `examples/eval/main.rs` (still the `eval` example, so `cargo run -p b2-embed --example eval` and every Makefile target are unchanged) plus one module per block of the report: labels (sets + lint), metrics, retrieval, discovery (per-mate, strangers, z dump), fold (GH #200 bake-off), evidence (D2 calibration + bake-off), tail (GH #206 bake-off + cross-bench join), dense (the single-domain fixture), ablation (--stemmer / --sweep), report (default table + JSONL row), instrument (batch ≡ single, timed embed, pool-blind warning) and gate (the exit-gate constants and assertions). main.rs is the run's order of operations and nothing else. Moved verbatim apart from visibility, imports and doc links, with three shape changes: - `result_row`'s 18 positional parameters become `RunId` (who scored the run; also taken by `dense_row`) plus a `RowInputs` view whose four default-row-only readings travel together as `Calibration`. Every row key and value is unchanged; the ablation rows' calibration keys still read null. - The stemmer and sweep blocks become `ablation::stemmer`/`ablation::sweep` over a `Baseline` view of the default passes; the stemmer's `.expect` on the unstemmed BM25 pass is gone (the pass is handed over by `if let`). - The exit gate becomes `gate::passes`, returning `false` where `run` returned `Ok(false)`. Also reattaches `QueryEvidence`'s doc comment, which had drifted onto `ServedRow`, and points docs/evals.md's exit-gate link at eval/gate.rs. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01GCwoP7o76vxQ8rEQq7zM8w
19 inline copy-open(-reindex) sequences use common::opened_vault / reindexed_vault, and 11 inline opens of .b2/b2.sqlite use common::index_conn. The identical inbound() of mv.rs and props.rs, the near-identical write_note of discover.rs and why.rs, and the token callbacks (ask.rs stream_all, why.rs keep_streaming and the inline closures) move into common as inbound, write_note, keep_streaming and stream_into. No assertion changes. The Capture writers in logging.rs and discover_query_count.rs stay deliberate copies. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01GCwoP7o76vxQ8rEQq7zM8w
…e SQL order MoveSet looks moved notes up by old path; sorting in Rust keeps that invariant local instead of resting on SQLite's BINARY collation matching str ordering. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01GCwoP7o76vxQ8rEQq7zM8w
…cleanup-y3s69c Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01GCwoP7o76vxQ8rEQq7zM8w
…leanup-y3s69c Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01GCwoP7o76vxQ8rEQq7zM8w
… drop the unbuilt graph join - Vault::link renders its b2_relations entry with link::render_relation, the inverse of the parser that reads it back. - Every resource op checks the inventory in the façade (require_resource); rm::delete_resource no longer checks a second time. New Vault::resource_path gives the desktop's Open in system default one inventory-checked call instead of borrowing explain_resource's backlink query to validate. - ingest resolves a note-relative Markdown target with pathspec::parent_dir / pathspec::join_relative, next to relativize, its inverse (round-trip tested). - search::graph_filtered_search had no caller outside its tests: no command needs "nearest chunks within k hops", and discovery is its complement. It goes, with db::vector_search_all and db::chunk_note_map, which existed only for it. index-engine.md no longer calls it the substrate b2 similar runs on; it says what similar actually reads. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01GCwoP7o76vxQ8rEQq7zM8w
…a 7) notes.type, notes.description, notes.updated (+ notes_type_idx) and the whole note_aliases table were written on every projection and read by nothing: NoteView takes type/updated straight from the parsed file, the resolver never consulted aliases, and no embed prompt used description. They go, with the NoteRow/NoteFields fields that fed them and the test that pinned the alias table. SCHEMA_VERSION 6 -> 7, so an existing index rebuilds on open and re-embeds (S5: a version bump plus a rebuild). The spec said more than the code did, so it now says what is true: data-model.md §1 no longer claims description feeds the embedding prompt or that aliases resolve links (both round-trip, neither is read yet), and type no longer "defaults to note". index-engine.md's schema sketch matches the DDL. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01GCwoP7o76vxQ8rEQq7zM8w
…s name the split modules executeMove adopted the moved resource only after its picture's bytes arrived, so the watcher pulse the move itself causes could find state still naming the old path and warn "no longer on disk" about B2's own move. The path is adopted first; the picture follows if the resource is still open. Comments that pointed at render.ts for code that now lives in markdown.ts / settingsview.ts / keysview.ts / chatview.ts / graphview.ts / explainview.ts / widgets.ts name the module it moved to, including the sanitizer's trust-boundary header. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01GCwoP7o76vxQ8rEQq7zM8w
…is true From review of the cleanup: - b2-embed's Metal-to-CPU fallback had become a tracing event, which neither adapter shows unless logging is asked for. A fallback changes the embedding space's identity (no @metal tag), so a Metal-built index then reads as a model change; the stderr notice explaining why is back, and the tracing dependency it pulled in goes. - mv.rs claimed a move rewrites "exactly the links that point at it". It rewrites by written text within an inbound file, so a second link with the same text that resolves elsewhere is rewritten too (true before this cleanup as well). The docs now name that gap instead of denying it; the move_note doc no longer promises a Markdown-form link always dangles. - The three a_*_move_leaves_the_index_a_rebuild_would_project tests compared only edges and resources; projection() now compares notes and chunks too, so the names hold. - Stale mentions of the dropped note_aliases table (mv, rm, index-engine.md) and a SCHEMA_VERSION doc entry for 7. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01GCwoP7o76vxQ8rEQq7zM8w
A warm eval loaded LocalEmbedder up to three times: inside provision()'s "already installed" check, again right after, and a third time for the dense fixture. examples/common gains load_or_provision (load first, provision only if that fails); eval shares one Rc-backed SharedEmbedder across its fixtures, and score_dense takes the embedder instead of loading its own. stability and groundedness use load_or_provision too. Verified on full --stemmer --sweep runs: identical stdout and results rows against the pre-change binary. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01GCwoP7o76vxQ8rEQq7zM8w
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01GCwoP7o76vxQ8rEQq7zM8w
|
Important Review skippedToo many files! This PR contains 132 files, which is 32 over the limit of 100. To get a review, reduce the PR to 100 files or fewer by splitting it into smaller PRs or changing its base branch. Upgrade to a paid plan to raise the limit. This review couldn't start because sufficient usage credits or metered capacity aren't available. Add credits or update usage-based reviews in the billing tab, then retry. ⚙️ Run configurationConfiguration used: Organization UI Review profile: CHILL Plan: Advanced Run ID: 📒 Files selected for processing (132)
You can disable this status message by setting the 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. Comment |
Summary
Restructure the
b2-clicrate from a monolithicmain.rsinto a modular command-based architecture. This refactoring extracts command implementations into separate modules (args,chat,read,write,reindex,cancel,error,logging,wiring) while maintaining the same CLI interface and behavior.Key Changes
Module extraction: Split
main.rs(310 lines) into focused command modules:args.rs: clap definitions and vault root resolutionchat.rs:ask,why, andchatREPL implementationsread.rs:neighbors,explain,search,similarcommandswrite.rs:add,write,mv,rm,linkcommandsreindex.rs:init,reindex,statusimplementationscancel.rs: Ctrl-C cooperative cancel handling (SIGINT, cross-process)error.rs: CLI error type and user-facing message translationlogging.rs: Opt-in structured debug loggingwiring.rs: Embedder and chat provider injectionCore library refactoring: Reorganize
b2-core::vaultto separate concerns:vault/chat.rs: Flow ④ orchestration (grounded chat, Why? tool)vault/views.rs: Display-ready types for CLI and desktop IPCReindexProgressto public exportssnippetmodule for one-line excerpts (search hits, citations)Eval harness expansion: Restructure
b2-embed/examples/eval/from singleeval.rsinto focused modules:main.rs: Entry point and orchestrationfold.rs,tail.rs,evidence.rs: Bake-off implementations (GH Phase B: the discovery disclosure bake-off — a default-view fold forb2 similar(runs #197 Phase 2, on D1 as redrafted) #200, Phase C2: per-hit tail honesty for search — the fold where a real query's evidence runs out (D2 Step 2) #206, Phase C: search evidence honesty —b2 searchanswers zero when the vault holds no evidence (implements D2) #201/Phase D: the search evidence verdict reaches the surfaces — CLI, JSON, desktop, and the exit-gate moves (lands #201; #200 shipped no fold) #202)discovery.rs,dense.rs: Scoring on orthogonal corpusreport.rs: Per-query tables and aggregatesretrieval.rs,metrics.rs: Scoring primitivesgate.rs: Exit gate assertionslabels.rs,ablation.rs,instrument.rs: Supporting infrastructurecommon/mod.rs: Shared helpers for eval examplesDesktop improvements: Add
slot.rsfor single-in-flight cancellable façade operations andstate_file.rsfor host state persistenceDocumentation updates: Clarify module purposes and link grammar in comments; update data-model.md and index-engine.md references
Implementation Details
dispatchfunctionb2 reindex --cancelboth flip the sameCANCELflag, read by reindex embed loop and chat token callbackB2_DEBUGfor detail)wiring.rs: embedder choice (real/fake) and chat provider injection--jsonoutput contracthttps://claude.ai/code/session_01GCwoP7o76vxQ8rEQq7zM8w