Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
46 changes: 12 additions & 34 deletions .github/workflows/ci.yml
Original file line number Diff line number Diff line change
@@ -1,19 +1,9 @@
# The repo's one enforcement point (GH #87 §2). Everything it *checks* lives in `make ci`, not
# here — this file is only the environment that recipe needs. That split is deliberate: a check
# re-specified in YAML is a check that can drift from the one you run locally, and until this
# workflow existed CLAUDE.md's "what CI runs" was aspirational. Now `make ci` is literally it.
# The repo's one enforcement point (GH #87 §2). Every check lives in `make ci`; this file is
# only the environment it needs, so CI can't drift from a local run.
#
# Runner: macos-latest. B2 is developed and shipped on macOS only — Metal is macOS-only, and the
# Xcode Command Line Tools that `b2-desktop`'s WebView links against are preinstalled there (and
# supply `make`, so no separate task-runner install step is needed either), so the whole
# workspace (including the Tauri host) builds with no platform setup step at all. A Linux
# runner would need the webkit2gtk/libsoup apt block and would red-build on portability breaks
# nobody ships against.
# macos-latest: B2 ships on macOS only, and the Xcode CLT there supply the WebView and `make`.
#
# Model-free by construction, so no `b2 init` step and no model download: `b2-core` never links
# candle, and `b2-cli`'s tests spawn the binary under `B2_EMBEDDER=fake` (crates/b2-cli/tests/cli.rs).
# The real-model work — `make eval` and friends — is non-deterministic and stays out of CI on
# purpose (CLAUDE.md, "Keep `cargo test` fast, deterministic, and model-free").
# Model-free: no `b2 init`, tests run under `B2_EMBEDDER=fake`. Real-model evals stay out of CI.

name: CI

Expand All @@ -23,8 +13,7 @@ on:
pull_request:
workflow_dispatch:

# A new push to a branch makes the in-flight run for that branch obsolete; cancel it rather
# than pay for both. `main` gets its own group so a merge is never cancelled by a PR push.
# A new push cancels the branch's in-flight run. `main` has its own group.
concurrency:
group: ${{ github.workflow }}-${{ github.ref }}
cancel-in-progress: true
Expand All @@ -42,40 +31,29 @@ jobs:
steps:
- uses: actions/checkout@v7

# No toolchain action: rust-toolchain.toml is already the single source of the pin
# (1.96 + rustfmt + clippy), and the no-arg form installs exactly what it names — so CI
# can't silently run a different toolchain than a local `cargo` invocation resolves.
# No toolchain action: the no-arg form installs exactly what rust-toolchain.toml pins.
- name: Install the pinned Rust toolchain
run: rustup toolchain install

# Caches ~/.cargo and target/. Load-bearing here: the workspace builds every dependency
# at opt-level 3 (see Cargo.toml's profile block), and candle dominates a cold build.
# Dependencies build at opt-level 3 (Cargo.toml), so a cold build is slow.
- uses: Swatinem/rust-cache@v2

# Pinned rather than lts/* so a new LTS can't change the runner under us; 24 is what the
# repo is developed against. The floor is vite 6's engines field — Node 18, 20, or 22+.
# Pinned, not lts/*, so a new LTS can't change the runner under us.
- uses: actions/setup-node@v7
with:
node-version: '24'
cache: npm
cache-dependency-path: ui/package-lock.json

# `npm ci` — not `npm install` — because CI is where lockfile drift should be an error
# rather than a silent rewrite. `make ci`'s own `ui-install` prerequisite is a no-op after
# this, and the tree-clean assertion below catches it if it turns out not to be.
# `npm ci` so lockfile drift is an error, not a silent rewrite.
- name: Install frontend dependencies
run: npm --prefix ui ci

# The gate. Same command, same stages, same order as a local run. No separate task-runner
# install step: `make` is already on the runner (macos-latest ships Xcode CLT).
# The gate: the same command as a local run.
- run: make ci

# Nothing above is allowed to edit a tracked file. This catches two real invariants at
# once: `ui-install` rewriting package-lock.json (drift between package.json and the
# committed lockfile), and the engine suite mutating fixtures/golden-vault/. The tests
# still copy the fixture into a tempdir first (CLAUDE.md, "Conventions"), but since
# GH #170 the deeper claim is that they wouldn't need to: B2 makes no unbidden writes,
# so indexing a vault leaves every byte of it alone. This is what stands behind that.
# Nothing above may edit a tracked file: catches lockfile drift and any write to
# fixtures/golden-vault/ (no unbidden writes, GH #170).
- name: Assert the gate left the working tree clean
run: |
if ! git diff --exit-code; then
Expand Down
24 changes: 6 additions & 18 deletions Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -8,25 +8,13 @@ members = [
"crates/b2-desktop",
]

# B2 — local-first Markdown PKM with AI connection discovery.
# Design lives in docs/; this workspace is the index engine
# (docs/index-engine.md), built step 0→5 against the
# golden-vault fixtures in fixtures/golden-vault/ (docs/data-model.md §8).
# B2: local-first Markdown PKM with AI connection discovery. Design lives in docs/.

# Build DEPENDENCIES optimized even in dev/debug builds, while keeping our own `b2-*`
# crates at opt-level 0 so the TDD loop and `cargo test -p b2-core` stay fast. Candle's
# embedder forward pass (matmul + transformer ops, spread across candle *and* its gemm
# matmul backend) is ~13× slower unoptimized, so a plain `tauri dev` / `cargo run`
# reindex was painfully slow (a 16-chunk embed batch took ~35s instead of ~2.5s). That
# mattered doubly for the desktop app: a reindex cancel is only observed at each embed-
# batch boundary (docs/index-engine.md), so a slow batch made the
# **Cancel** button feel stuck. The `"*"` glob optimizes every dependency (targeting
# just the candle crates missed the matmul backend and left it ~4× slower); the per-
# crate opt-0 overrides below exclude our own crates so their rebuilds stay instant.
# Tradeoff: a *cold* build (or a cleared cache, e.g. fresh CI) now compiles every
# dependency at opt-3 — a larger one-time cost, dominated by candle. It's a one-off,
# amortized by the target cache and unmatched by any warm/incremental rebuild (ours stay
# opt-0); `cargo test -p b2-core` stays light regardless since b2-core pulls no candle.
# Optimize dependencies even in dev builds, but keep our own `b2-*` crates at opt-level 0
# so rebuilds stay fast. Unoptimized candle (and its gemm backend) embeds ~13× slower, and
# a reindex cancel is only observed between embed batches (docs/index-engine.md), so a slow
# batch makes Cancel feel stuck. The `"*"` glob is needed to catch the matmul backend.
# Cost: a cold build compiles every dependency at opt-3.
[profile.dev.package."*"]
opt-level = 3
[profile.dev.package.b2-core]
Expand Down
Loading