Skip to content

0.24.2

Latest

Choose a tag to compare

@github-actions github-actions released this 29 Oct 07:13
· 55 commits to main since this release
3477059

[0.24.2] - 2026-10-29

Changed

  • The PyPI wheel channel is retired: pyproject.toml, uv.lock, the maturin builds, and every wheel/publish job are gone. A v* tag now cuts the GitHub Release (changelog notes) while release-rust.yml attaches the binary tarballs, updates the Homebrew tap, and publishes to crates.io; Homebrew, cargo binstall, and the tarballs are the install channels.
  • cairn uninstall reports the running binary and its channel-appropriate removal command instead of driving pip/pipx/uv; cairn report no longer prints a python line.
  • Local CI replication (scripts/ci-local.sh) runs on a debian container with only the rust toolchain and prek (the python image, uv provisioning, and the wheel-smoke/build jobs are gone).
  • cairn impact --guard [--threshold N] replaces the skill's scripts/impact_guard.py helper: a cycle-carrying result above the threshold prints a name-collision warning and the dataflow-index fallback instead of the raw dump (--json gains a guard object), and the agent skill no longer ships a Python script.
  • Dev tooling moved to cargo xtask (the parity and CLI-golden oracles, canonical-dump tools, perf harnesses, and the repo gates).
  • scripts/install.sh builds from a checkout with cargo install (no Python required), scripts/uninstall.sh forwards to cairn uninstall, and the SCIP test fixture regenerates with cargo xtask scip-fixture.
  • cairn upgrade queries PyPI through curl instead of a Python interpreter.
  • Release bumps run through cargo xtask bump (commitizen retired): it moves the Cargo workspace version, Cargo.lock and pyproject.toml together, commits and tags; --dry-run drafts the changelog section.
  • wheel_smoke.sh probes the wheel with shell tools (unzip/7z, jq, od) instead of inline Python.
  • The commit gates run under prek (prek install) instead of pre-commit, with native yaml/toml/merge-conflict/large-file hooks.
  • cargo xtask comment-style gates long comment and doc blocks in the Rust crates (shrink-only baseline), and cargo xtask verify-protection requires the Rust CI gates and fails on required checks no workflow emits.
  • Rust doc comments state contracts directly: provenance pointers to the retired Python implementation (parser registry, scanner filter tiers, pragma parity, MCP tool text contracts) and to deleted Python-era scripts (tests/fixtures/golden/regenerate.py, scripts/regen_scip_proto.sh) are gone; the golden-test normalization docs describe the committed expected.json schema instead.

Removed

  • CAIRN_RUST_BIN: CAIRN_BIN is the single engine-binary override (daemon start, cargo xtask kg-dump, local CI scripts).
  • The stray-daemon sweep no longer recognizes python-launched cairn serve processes.
  • The cairn upgrade command (and its --check flag): it existed to pip/pipx/uv-upgrade the wheel installs; every remaining channel (Homebrew, cargo, tarball) upgrades through its own package manager. The upgrade --check cli-golden case is retired with it.
  • The ci.yml wheel/sdist packaging canary and the rust-parity nightly wheel-smoke job.
  • The PyPI wheel channel's packaging files (pyproject.toml, uv.lock) and scripts/rust_migration/wheel_smoke.sh.
  • Retired stubs and upgrade shims: the download-reranker command; the build --lsp, init --import-docs, init --from-legacy and embed --install-deps/--download-model/--adopt-server-model flags (now unknown-argument errors); the doctor agent_hooks check and every migration of python -m cairn.hooks.claude_hooks hook commands; pruning of the retired impact_guard.py skill file; unloading the dev.cairn.sse LaunchAgent; the automatic ./cairn legacy-store copy in init; the dashboard's one-segment /wiki/<page> redirect; and the rebuild of pre-(symbol, repo) dataflow tables.
  • The optional cairn-intel-ingest PDF converter package and the CAIRN_SIDECAR_PYTHON override: knowledge ingest always converts PDFs natively (paragraph text). The repo now carries no Python code outside test corpora.
  • cairn uninstall no longer probes a Python interpreter for source-tree build debris of the retired Python package.
  • The Python-only dev gates: the pytest and pip-audit/bandit CI jobs, the ruff and debug-statements pre-commit hooks, scripts/install-dev-hooks.sh, and cargo xtask verify-no-code-change.
  • Dead Python-era fixture tooling: the tests/fixtures/__init__.py package markers and tests/fixtures/scip-indexing/generate_heavy.py (unrunnable in this tree — it required the retired src/cairn/parsers/_scip_pb2.py). The committed parser-golden and SCIP fixtures remain; both regenerate from git history when needed.
  • The openai value of CAIRN_EMBED_BACKEND and CAIRN_EMBED_OPENAI_MODEL: an unrecognised backend name resolves to the local hash embedder.
  • The memory_validity.backfill schema_meta marker: stores no longer write it.

[0.24.1] - 2026-10-26

Fixed

  • The maturin sdist carries the declared LICENSE file (PyPI rejected the 0.24.0 sdist with License-File LICENSE does not exist); the 0.24.0 wheels already on PyPI are complete and unaffected, so source builds (pip install --no-binary) work again from 0.24.1. Release-tooling fixes that surfaced during the 0.24.0 Windows leg ride along: wheel_smoke.sh resolves the installed engine and the venv interpreter by their real .exe spellings before the extensionless forms (msys ls/stat report the extensionless name as existing beside the .exe, which direct execution tolerates and argument passing does not).

[0.24.0] - 2026-10-26

Added

  • Native cairn communities (rust-only spec, FR-001, decision D-R01): a seeded, deterministic Louvain partition computed in the engine over the structural edge kinds and weights the networkx sidecar used, writing the same communities and symbol_communities tables with the same CLI flags (--db, --top-k) and output shape; acceptance is property-based — the same seed always yields the same partition and fixture modularity is at least the networkx result within 1e-6 — pinned by owner tests and a cli_golden case.
  • Windows x86_64 engine support (rust-only spec, FR-007, decision D-R09): every Unix-only call site is gated or replaced (API keys via BCryptGenRandom, platform home resolution, PATHEXT-aware executable lookup, the bundle lock on std file locking), the daemon lifecycle runs as a detached process with a pidfile (tasklist liveness, taskkill escalation, conservative never-kill-unverified sweeps), CI gains a windows-latest leg running clippy plus the full workspace test suite, and release publishing gains the win_amd64 wheel leg and an x86_64-pc-windows-msvc tarball.
  • Local CI mirroring GitHub's workflows in Apple containers (scripts/ci-local.sh via make ci-local/ci-local-rust/ci-local-parity): the python jobs plus the rust gates (fmt/clippy/workspace tests/cargo-deny) and the parity oracles (self replay, cli golden, G2 base) run in a clean Linux container with persistent caches, an isolated per-arch cargo target dir, and a derived image carrying the runner tooling; scripts/rust_migration/win_clippy_check.sh additionally lints the Windows cfg arms from macOS.
  • Native document conversion in the engine (rust-only spec, FR-002/FR-003): knowledge ingest --file converts .docx to markdown (headings, paragraphs, emphasis, lists, tables, links) and extracts .pdf text with no Python present; the optional high-fidelity PDF converter moved to its own cairn-intel-ingest package (pip install cairn-intel-ingest) and is used automatically when importable, keeping the AGPL dependency (pymupdf4llm) out of the engine; the cairn-intel[ingest] extra is retired.

Changed

  • The daemon reuses SQLite connections across tool calls (rust-only spec, FR-008, decision D-R05): each worker parks read and primary connections and revalidates the store's identity on every checkout — (dev, inode) on Unix, (len, mtime) on Windows — reopening after a build swap instead of paying a fresh connection per call; measured small-store tool p95 improves up to 3.5x (find_definition 0.983ms -> 0.281ms on the self corpus), and scripts/rust_migration/baseline.py --compare gates every tool at 1.5x.
  • The closure performance gate is re-ruled to absolute, insert-bound numbers (rust-only spec, FR-009, decision D-R06): the self pinned store must close in <=275ms median with <=11MiB peak WAL (3x/2x the measured 91ms/5.4MiB), executable via scripts/rust_migration/closure_bench.py --compare — the former >=5x-vs-Python target was unreachable because the closure is insert-bound in both engines.
  • cairn build --lsp now exits 1 naming the pyright-pass retirement instead of silently doing nothing (rust-only spec, FR-012, decision D-R07); --lsp=false stays a default-equal no-op.
  • The PyPI wheel is the engine binary (rust-only spec, FR-004/FR-005/FR-006): maturin builds cairn-intel from the cargo workspace with the native cairn executable as its only payload (py3-none-<platform>, no Python launcher or module), so pip install, pipx install and uv tool install cairn-intel run the engine directly; wheels ship for manylinux and musllinux x86_64/aarch64 and macOS x86_64/arm64, the sdist compiles the engine with a Rust toolchain (vendored sqlite-vec and grammar sources included), and scripts/rust_migration/wheel_smoke.sh probes the installed product on each native release leg.

Fixed

  • Windows portability across the engine: canonical paths are stripped of the \\?\ verbatim prefix so bundle-root containment, the task queue and path joins behave like Python's Path.resolve(); a store swap moves the live file aside before renaming the staged build into place (rename-over-open is denied while a parked connection holds the store) with a copy-into-place fallback; executable lookup treats a readable plain file as a candidate (no exec bit exists) and honors PATHEXT; git hooks embed forward-slash homes under git's sh; and .gitattributes pins LF text plus binary pdf/docx/kg fixtures so the frozen goldens are byte-stable on every checkout.

Removed

  • The Python runtime is retired end to end (rust-only spec, FR-010/FR-011): src/cairn/ is deleted (the networkx communities module went first — cairn communities is native and deterministic), the [graph-analytics] extra and the dev-time networkx/mypy pins are gone, and the converter package's tests moved next to it under packaging/cairn-intel-ingest/. CI drops the mypy leg and the bandit-over-src scan (bandit now advises over scripts/ and packaging/; pip-audit still audits the dev environment and the converter package), the comment-style ratchet re-scopes to the dev-script trees, and the migration oracles lose their Python-engine recording arms (cli_golden.py --reference, parity's venv-cairn lookup) — they are replay-only. Docs swept to the binary-only install: README install/development sections, docs/cli-reference.md, docs/architecture.md, docs/indexing.md, docs/release-checklist.md, AGENTS.md's CLI fallback, CONTRIBUTING, NOTICE; scripts/install.sh drops the retired --semantic path. cairn prs, bench and eval stay retired — the Python engine they measured is gone and the binary carries no arm for them (decision D-R10 in specs/rust-only/decisions.md).

[0.23.1] - 2026-10-25

Added

  • PyPI wheels bundle the rust engine binary (spec D-011, exec-shim binding): the daemon defaults to rust when the bundled binary is present (CAIRN_RUST_BIN env > bundled package path > PATH; the python fallback and CAIRN_DAEMON_IMPL precedence are unchanged), the release pipeline injects the per-platform binary into the wheel builds while the sdist stays binary-free, and scripts/rust_migration/wheel_smoke.sh proves the installed product end to end (fresh venv, C-4 machine contracts, binary discovery).
  • The Python engine is removed: the PyPI console script execs the bundled Rust binary for every command (the Python CLI runs only when no binary is present), the remaining CLI surface (task/wiki/hooks, lifecycle/ops, graph analytics, knowledge/workflow/memory/compass, ask/context/review) is ported to cairn-cli, and the parity oracles are frozen at the last Python capture (the Rust goldens are the spec of record).

Fixed

  • Rust store migration rebuilds a legacy dataflow table to its (symbol, repo) key whenever its shape requires it, even when the migration marker is already recorded (repairs stores opened by 0.23.0 binaries), inside one transaction; the Rust daemon's writable tool connections apply the store pragmas (WAL, mmap, sqlite-vec).
  • Config files are never overwritten when they cannot be read or backed up: rust install-agents aborts a client whose existing config cannot be backed up (matching python), and saving dashboard settings refuses to replace an unparseable config.json and keeps its non-scalar keys (rust and python).
  • Rust cairn build --staging swaps the staged store into place under the build lock instead of leaving <db>.tmp behind; rebuilding into a populated store replaces it (full build) or only the named repo (--repo) instead of failing on id collisions; rust incremental writers and the watcher take the build lock.
  • Call, contains and decorator edges are attributed to the enclosing same-named owner instead of the first same-named symbol in the file; trace_flow reports each node and cycle at its shortest depth and explores its subtree from there (rust and python).
  • Incremental update detects files changed since the last index (not only uncommitted ones), reads git paths NUL-separated and reports git failures; incoming-edge repair keeps receiver and arity signals, spans repos, chunks large name sets, leaves LSP-upgraded edges alone and keeps closure/dataflow in sync, so incremental results match a fresh build (adds nullable edges.ref_name, receiver_type, call_arity); import-aware resolution no longer rewards deeper wrong-module matches (rust and python).
  • Flaky flow_golden rust test: TempStore scratch dirs named from wall-clock nanos collided under macOS parallel test threads; now named from a process-global counter.
  • install-agents client-schema conformance, verified against each agent's official docs: agy (Antigravity) workspace installs now write the documented .agents/mcp_config.json instead of ignoring workspace scope, its global path is ~/.gemini/config/ on every Unix (no XDG deviation), and its entry drops the undocumented type field; Cursor entries drop the type field (its remote schema documents url+headers only); the install probe and cairn doctor audit both new paths.

[0.23.0] - 2026-10-20

Added

  • The rust migration's first full campaign (spec specs/rust-migration/, phases 0-3): a six-crate rust workspace under rust/ (store, query, parse, build, daemon, cli) — an rmcp streamable-HTTP daemon serving all 26 tools golden-equal against pinned store snapshots, the rayon build path (parsers for all 15 grammars, resolver triage, streaming transitive closure, incremental graft, SCIP import via prost), the clap CLI byte-compatible on the machine contracts, the dashboard on minijinja, and a CAIRN_DAEMON_IMPL=rust opt-in switch (cairn connect --impl rust). Distribution is planned dual-channel: PyPI wheels (maturin, abi3) plus Python-free release binaries. Parity is enforced by deterministic oracles — MCP golden fixtures with frozen replay arguments, the canonical .kg dump/diff, cli_golden.py, and g2_check.sh (python-built and rust-built stores dump-equal).
  • CI gains a rust leg (fmt, clippy -D warnings, workspace tests, cargo-deny, release build matrix including musl-static) alongside the python matrix.

Removed

  • The legacy docs/ and brainstorms/ trees, keeping the operationally referenced pages (architecture, cli-reference, deployment, review-checklist, release-checklist, mcp-tools) and the README header art.

Fixed

  • Second fix wave over the 2026-10-15 audit (the post-fix rerun's 158 findings; 152 fixed, 3 refuted with evidence, 3 deferred as design decisions). Highlights:
  • Data loss and crashes (both verified highs): a full build whose scan indexes nothing but skips refuses to replace a populated store (the non-staging sibling of the staging guard); worktree store seeding retargets only the main repo and drops sibling rows instead of colliding, and cleans up its partial store on failure.
  • Agent installs: registrations derive from the daemon's actual bind; the non-object config path preserves unrelated keys; the zcode mcpServers pop drops only cairn-stamped entries; skill-tree removal byte-matches templates so customized files survive; git hooks resolve worktree/submodule hook directories; the workspace registry registers under a lock.
  • MCP and daemon: record_memory commits the pooled connection; knowledge write tools refuse under CAIRN_READ_ONLY; the launchd plist carries the API key; serve restart keeps the daemon's own bind; serve stop verifies shutdown; sync runs under the build lock and auto-refresh derives its workspace from the store it serves.
  • Search and graph: a raised dense leg degrades to the sparse/fusion rescue; an explicit rerank override forces reranking on; trace flows resolve callees by symbol id; the query family honors CAIRN_READ_ONLY; dataflow rows scope, dedupe, and surface analysis errors and ambiguity; grep reports real lower-bound dropped counts; Java generic constructor calls and decorated Python methods index correctly; C# enum members qualify under their enum.
  • Tasks and knowledge: stale claims are reclaimable, claim and completion run under the bundle lock, raced drops stay dropped; LLM extract failures return immediately instead of queueing duplicates; knowledge writes take the bundle lock; wiki modules match the stats bucketing, manifests validate and save under the lock, and refine de-collides page ids.
  • Dashboard and eval: settings saves require a loopback Host (closing the DNS-rebinding CSRF path known since the 2026-09-30 audit), dropped values are reported, unicode-digit ids degrade instead of 500; telemetry buffers drain by identity; eval validates its inputs and maps parser failures cleanly; the compass LLM write path is critic-gated with collision-safe coverage keys.

Changed

  • Full-coverage fix campaign over the 2026-10-15 logic-flow audit (364 deterministically-enumerated flows, 215 findings; every finding re-verified before fixing — refuted and by-design findings documented, not papered over). Highlights by area, all with regression tests:
  • Data loss and crashes: cairn build --staging refuses to swap an empty build over the live store; cairn config renders a clean line for a server embed backend without CAIRN_EMBED_BASE_URL; knowledge imports get unique slugs for same-stem files instead of overwriting one concept; the compass router's L5 knowledge layer (and the context/ask_compass memory sections) match concepts again through a shared relativize_id helper; a dozen commands guard the store before opening it.
  • Graph and indexing: the dataflow cache is keyed (symbol, repo) with an in-place migration, purges stale rows on rebuild, and surfaces per-symbol analysis errors; C# generic base types emit inheritance edges; plain Python imports store normalized dotted module paths; pack expansion resolves callees by symbol id; get_callers/get_callees order before LIMIT; the watcher sweeps deleted untracked files; scip join-anomaly accounting counts dropped targets.
  • MCP server and daemon: --host reaches the daemon plist/exec argv; serve status probes the daemon's configured bind; cross-store daemon reuse is refused; serve stop survives launchctl failures; generate_flow and recall_memory honor CAIRN_READ_ONLY; structured impact bypasses the dataflow cache.
  • Memory and knowledge: uniform write→embed→commit sequencing with embed-failure deferral across record/evolve/capture/capture-event/batch-critic; one namespace guard closes cross-namespace demote/promote/evolve; promotion decisions are recorded before the write; knowledge deletes remove embedding rows before unlinking and normalize ids under relative/symlinked CAIRN_HOME; supersede chains rebuild start-anchored over real edges.
  • CLI contracts: strict-refresh and git failures map to clean errors across the query family; cairn update exits non-zero on reindex errors and resolves A...B diff ranges; three-dot staging; --dry-run is honored on every compass write path; wiki search scopes to wiki pages and export confines paths to the target directory; the dashboard refuses saves under CAIRN_READ_ONLY and exits 0 on Ctrl-C; agent reinstall rewrites stale daemon URLs and rotated bearer keys and preserves foreign servers in stray opencode configs; git hooks key ownership on the template marker instead of a "cairn" substring.
  • Benchmarks and eval: timed suites pin CAIRN_RERANK and CAIRN_MAX_RESULT_CHARS inside try/finally; scaling comparisons read the points shape and regressions exit 2; unbaselined labels are visible; eval reports carry lexical-fallback warnings; the perf-suite rerank spy no longer pins the lazy queries proxy (which poisoned later tests in a session).

Changed

  • Breaking: the MCP server now speaks Streamable HTTP only — one shared loopback daemon on 127.0.0.1:9876 (read-write by default, --read-only opt-in). The stdio and SSE transports are removed; cairn serve --transport <any> exits non-zero naming the daemon URL and cairn connect. Agent registrations are URL + key form for the 8 supported clients (Claude Desktop reported unsupported — no remote capability); cairn init generates a user-only CAIRN_MCP_API_KEY embedded in generated configs; cairn doctor --fix repoints legacy stdio/SSE registrations; registration verification probes /healthz instead of spawning processes.
  • cairn connect starts or reuses the single daemon (flock-guarded, stray-sweeper enforced) and prints its URL; serve start|stop|status|restart manage the HTTP daemon (launchd on macOS, daemonize + pidfile elsewhere), and the sweeper now recognizes interpreter-prefixed serve processes.
  • MCP rev 2026-07-28 features (MRTR, subscriptions/listen, header validation) arrive via SDK bump only — no hand-rolled transport code (mcp pin unchanged).

[0.22.0] - 2026-10-10

Added

  • Parser conformance matrix in docs/indexing.md: one row per golden-fixtured language (Rust joins as the 15th), rendered by scripts/gen_conformance_table.py between markers and kept fresh by a staleness test; every row is green or carries an explicit tier mark (no import/hierarchy edges).
  • Pyright upgrade pass is now budgeted and on by default: cairn build upgrades ambiguous Python call edges when pyright is available, capped by a wall-clock budget (60 s default) and a per-build edge budget (2,000 default), both configurable via cairn.json (lsp.budget_seconds, lsp.edge_budget); --lsp forces an unbounded pass, --no-lsp skips. Upgrades persist, so a budget-stopped pass resumes from the remainder on the next build, and the report carries probed/budget_hit for observability.
  • Comment sweep: the comment-style baseline fell from 1213 to 226 grandfathered violations (81% removed) across telemetry, memory, graph, mcp_server, cli, dashboard, parsers, knowledge, and agent_install, with 321 migration ledger rows landing 317 durable rationales in tribal memory (Why/How bodies, ledgered before deletion) and executable ASTs proven identical per area; python -m cairn.cli.system.audit_status|comment_style no longer emits the runpy double-import warning (deferred collector imports in report.py), and scripts/regen_scip_pb2.sh now emits the trimmed _scip_pb2.py header so regeneration cannot reintroduce debt.
  • Quality ratchet: make audit-status prints remaining-vs-total audit findings by priority from docs/audits/2026-10-02.md plus a strict .status.json fix sidecar, and make comment-style enforces the AGENTS.md comment contract over src/ through a machine-generated shrink-only baseline (comment-style-shrink re-derives it after fixes; growth fails). Both gates run as the check-comment-lengths pre-commit hook and the quality-ratchet CI job, expose ARGS=--json, and surface as a count-only quality_gates section in cairn report. The legacy root architecture.html and audit report moved to docs/diagrams/ and docs/audits/.
  • Rationale indexing: cairn build captures comments whose text begins
    NOTE:, WHY:, or HACK: in every language with a mapped comment node
    type and persists them in an additive rationale table, each record
    attributed to the innermost enclosing callable or type whose span
    contains the comment line (file-level otherwise; one record per comment
    with continuation lines folded into the text; docstrings never produce
    records, and TODO:/FIXME: markers are out of scope). The new
    cairn rationale command (--symbol/--file targets, --db, --json)
    prints records ordered by line with kind tags (note/why/hack); MCP
    explore output adds a gated Rationale section when any matched symbol
    has records and omits it otherwise, with the MCP tool count unchanged;
    incremental reindex and cairn update delete and re-derive each affected
    file's rationale rows together with its symbols, so removed markers never
    linger.
  • Freshness hooks: cairn update --diff-ref A..B scopes changed-file
    detection to a git ref range (range diff only — the untracked pass and
    stat fallback are skipped, and both endpoints are pre-validated so a bad
    ref is a clean error); cairn hooks install/uninstall manage a
    post-commit hook (backgrounds a ranged update after every commit — the
    previous commit resolves from the reflog, with an empty-tree range
    fallback on a repo's first commit — plus cairn validate-paths --mark,
    output discarded so the commit never waits) and a post-checkout hook
    (branch switches only; file checkouts no-op); cairn init --with-hooks
    installs both hooks opt-in and non-interactively. Hooks are never
    overwritten when foreign and removed only when cairn-marked; in a
    non-git workspace hooks install prints guidance and exits 1 while
    init --with-hooks warns and init still succeeds — no partial hooks
    either way.
  • cairn communities command: deterministic Louvain community detection over
    structural edges, persisted to the communities/symbol_communities
    derived tables with global and per-community hub symbols (--top-k,
    default 10); a store with no structural edges reports "no communities
    found". Requires the new [graph-analytics] extra (pip install 'cairn[graph-analytics]') — without it the command exits with the install
    hint and leaves the store untouched.
  • Dashboard /communities view: renders the partition with community sizes,
    hub symbols, and member drill-down (nav under Explore, after Graph).
  • HTTP serving: cairn serve --transport http exposes the full MCP tool
    registry over Streamable HTTP with bearer-key auth (CAIRN_MCP_API_KEY
    env or --api-key, flag wins), loopback-default binding with keyless
    operation restricted to loopback binds (a non-loopback bind without a key
    refuses to start), Host-header transport security per bind class, an
    unauthenticated /healthz probe, an optional --stateless mode for
    load-balanced deployments, and a read-only default; ships with a slim
    non-root Dockerfile (CAIRN_HOME=/data volume, HTTP entrypoint) and
    deployment docs. Local stdio and SSE transports are unchanged.
  • Memory stance overlay: memories carry an optional stance (preferred /
    tentative / contested, orthogonal to the lifecycle tiers);
    cairn memory record --stance preferred|tentative|contested declares a
    prior at record time, and the new cairn memory reflect recomputes
    stances across the store from supersession and citation-verification
    evidence — a memory superseded by a peer when the pair shares a symbol
    ref that still verifies is contested and names that peer, a memory whose
    refs-verified fraction dropped below its recorded baseline downgrades to
    tentative, a memory whose every cited ref verifies is preferred, and the
    evidence verdict overrides a declared prior on conflict while the prior
    survives when evidence yields no verdict. Reflect touches stance
    metadata only — never bodies, titles, tiers, or scores — and is
    idempotent and deterministic on an unchanged store; stance renders
    inline in cairn memory search / list output and MCP
    recall_memory / explore output.
  • cairn prs: one read-only PR-triage command fusing GitHub state with the
    local graph — a six-column open-PR list (number, title, branch, author,
    CI state conservatively classified pass/fail/pending/none from
    statusCheckRollup, review decision) sourced from the gh CLI over the
    workspace's remote; --impact PR|branch resolves the PR's diff (fetched
    via gh pr diff, never local git) against its base ref to changed
    symbols with dependents (depth + resolution), listing unindexed and
    deleted changed files explicitly and stating the local index's build age
    (Store: local index (built <age>|never)) so impact against a stale
    store is visible; --conflicts ranks open-PR pairs by shared-community
    overlap of their touched symbols (largest overlap first, PR-number
    tie-break), consuming the communities/symbol_communities tables
    (never building them) and printing a run cairn communities hint
    instead of failing when the tables are absent or empty; --json emits
    the full report payload (store/prs/impact/conflicts) for agents.
    Strictly read-only on both sides: the store opens read-only with no
    freshness refresh, and gh runs only as a subprocess with pinned
    list/view/diff argv (30 s timeout, no tokens read, logged, or passed) —
    gh missing, unauthenticated, timing out, or erroring surfaces as one
    actionable error with no partial table.

Fixed

  • A4 retro of the post-audit admin-merged PRs: cairn build --repo no longer
    crashes with an IntegrityError on files containing NOTE:/WHY:/HACK:
    comments and no longer strands a permanent building marker (the repo-scoped
    rebuild now clears rationale rows before their foreign-key parents); the
    dashboard Communities page renders an empty panel on stores created before
    community indexing instead of erroring.
  • Full-codebase audit wave (2026-10-02): 141 findings and 3 systemic clusters
    triaged; every P0, P1, and P3 finding is now closed (59 fixed, including 2
    added by the A4 retro; the remaining 87 P2 polish findings are tracked in
    the audit sidecar) across the graph, parsers, memory/LLM, MCP server,
    dashboard, wiki, knowledge, CLI, install/uninstall and scripts surfaces.
  • Hooks no longer silently no-op in source checkouts: python -m cairn.cli.main
    runs the CLI (missing __main__ guard), the hook fallback sites work, and the
    impact guard tolerates empty tool output.
  • Graph reads converge when a parser wheel is missing: unavailable-language
    files are skipped by the drift and build scans (recorded as parser-unavailable
    skips), dropped on reindex even when never indexed, and counted so strict
    refresh settles instead of raising on every read.
  • Incremental updates no longer drop cross-repo imports edges; delete-only
    diffs contribute blast-radius seeds; git octal escapes and quoted renames
    resolve to real paths; trace_flow visits each id once on diamond graphs;
    taint finds source→sink paths through the entry symbol.
  • Memory lifecycle on the declared Python 3.10 floor: Z-suffixed timestamps
    parse (purge actually purges), aware/naive timestamp mixes degrade instead of
    crashing decay and batch-critic, memory_refs delete the id form writers
    persist, task complete/drop races are locked with the claimer enforced, and
    refusals no longer report as drops.
  • Parsers: Swift properties and initializers emit symbols (goldens
    regenerated), Kotlin primary-constructor default call edges and TypeScript
    generic implements edges are restored, decorators land on their own
    declaration, and external SCIP indexers run inside the target repo.
  • Dashboard: chains span no longer errors on NULL timestamps, each skipped
    table shows its own reason, the dropped task filter works, template and
    knowledge-graph JS duplication collapsed onto shared helpers.
  • Wiki queue: manifest write failures surface to the CLI, --dry-run is
    honored in the --llm branch, enrich tasks carry the diagrams flag, and
    slug-collision page ids dedupe.
  • Destructive/privacy scope: uninstall --graph-only -y resolves the
    workspace store before deleting and never widens to all of ~/.cairn;
    config backups are no longer clobbered; hook payloads are redacted before
    truncation; non-object stdin JSON cannot crash hooks.
  • Scripts and verifiers: the SCIP protobuf regen self-copy fixed,
    install-hooks.sh delegates to cairn hooks install, benchmark suites restore
    environment variables, SWE-bench rows are validated before destructive use,
    and the datasource/ground-truth verifiers exit via their documented
    contracts.
  • Embeddings/rerank: malformed vectors skip instead of raising, the
    no-exist cache sentinel no longer counts as cached, OpenAI responses are
    envelope-validated without silent truncation, and the reranker model cache is
    locked.
  • Comment and doc hygiene: decision-log prose, incident stories, task/spec
    IDs and stale docstrings stripped or corrected across the codebase; the
    flagged duplication consolidated into shared helpers.

[0.21.2] - 2026-09-29

Added

  • Dashboard Memory detail pages: /memory/{tier}/{slug} renders a memory's
    body, identity, promotion history, signals and supersedes/superseded-by
    links, and the Memory list's titles link them.
  • Dashboard knowledge graph covers the whole knowledge layer: memory items
    join the docs as nodes (diamonds, tier as family) and their frontmatter
    supersede pairs draw as directed edges; the node-click inspect panel
    resolves memory ids and links the memory detail pages.

Changed

  • Test-suite audit: ~200 tests removed or trimmed under the "one owner test
    per contract / no implementation mirrors" policy — source-text mirror tests
    (CSS/JS greps, import allowlists, DRY lints), near-duplicates of owner
    tests, private-constant pins, and trivially-true smoke asserts. Distinct
    edge coverage was folded into the owning tests; test-only seams
    (MemoryStore/OKFMemoryStore/InMemoryMemoryStore, try_generate_index,
    reset_probe_cache, reset_tokenizer_mode) moved into the test suite or
    were deleted.
  • Documentation audit refresh: current CLI/MCP surfaces, 15-language parser
    set, optional extras, architecture diagrams, and benchmark methodology now
    match the code; the historical roadmap proposal is labeled as a snapshot.
  • Configuration docs cover CAIRN_NO_REFRESH and CAIRN_LLM_BACKEND.
  • Benchmark docs carry the current reference-local perf and 1,000-file
    scaling measurements.

Fixed

  • Generated Claude/Cursor hook commands shell-quote the interpreter path
    (pipx's Application Support location no longer exits 127), and
    reinstalling agent hooks replaces stale Cairn commands instead of treating
    any matching entrypoint as current.
  • Dashboard /health no longer returns 500 when the embed backend is a
    server family with no resolvable CAIRN_EMBED_BASE_URL: the failing probe
    degrades to None and the page renders it as unknown.
  • Dashboard history pagination no longer loops on the same page when the
    oldest row of an all-time page has a NULL invoked_at (legacy-shape
    tool_metrics tables): the next cursor is omitted instead of being
    unparseable.
  • cairn doctor markup-escapes the dynamic freshness hint, so a
    workspace-derived repo id containing [ cannot corrupt rich's markup.
  • C4 architecture diagrams now agree with the boot guard everywhere (25 MCP
    tools, groups 12/5/2/6; previously 22/24/9 across labels).
  • Agent-integration package data now ships only template/script file types,
    keeping generated __pycache__ bytecode out of sdists and wheels.
  • The dashboard first-render budget test judges the 200ms SC-1 budget on the
    best of 5 fresh-app trials, so full-suite scheduler noise no longer fails it.
  • Agent-suite benchmark docs report the modal est_tokens median (1,479 /
    99.7%) and scope the determinism claim to tool calls; estimated tokens vary
    by at most about 2% run-to-run.
  • The AGENTS.md template cairn install-agents writes carries the current
    tool-layer names, matching the repository copy (parity test added).
  • The oversized reserved-block pack test pins heuristic token costs, keeping
    it independent of the process-wide exact-tokenizer mode.
  • cairn build --repo no longer crashes with an integrity error on any repo
    embedded with the default multivector pass: _clear_repo deletes the
    repo's embeddings_mv rows before deleting its symbols.
  • cairn doctor no longer spawn-executes command/args/env read
    verbatim from workspace-owned registration config files: only an entry
    exactly in the shape cairn install-agents writes is probed, and any
    other shape degrades to a WARN naming the file.
  • The doctor memory-staleness check degrades to a WARN when its
    memory_refs read fails, restoring the diagnostic's never-raises
    contract.
  • CAIRN_ANN_BACKEND resolution lives in one shared helper, so the ANN
    index, doctor, dashboard, and MCP server agree on the empty-string
    opt-out.
  • C4 containers-level diagram says tree-sitter ×15, matching the parser
    registry and the context level (was ×14).
  • Incremental updates no longer cross-corrupt repos that share a
    repo-relative source path: every files lookup on the incremental path
    (reindex_paths, _find_tracked_file_row, derived-prestate capture) is
    scoped by repo_id, matching the UNIQUE(repo_id, path) schema.
  • cairn update and the live watcher in git repos now index newly created
    (untracked) source files: the changed-file signal merges
    git ls-files --others with git diff --name-only HEAD, so the watcher
    no longer erases their pending_sync staleness markers either.
  • One unreadable memory concept file no longer crashes every memory capture
    (record_memory, cairn memory record, session-end hooks): the
    supersession lookup skips concepts that fail to read instead of raising.
  • The dashboard's settings POST actions reject browser cross-site form POSTs
    (mismatched Sec-Fetch-Site/Origin, 403) before touching config —
    loopback binding alone does not stop a web page from posting to the port.
  • cairn memory consolidate unlinks the raw files it archives, so re-runs
    are idempotent instead of duplicating tribal/archived copies each time.
  • The legacy yaml evaluation path routes corpus: L4 queries to the L4
    retriever and emits the L4 report bucket (previously scored by the L5
    knowledge retriever and silently dropped).
  • get_callees applies the same per-file pending_sync staleness check as
    get_callers, and get_callees(structured=True) carries the banner
    through its structured model instead of dropping it at validation.
  • Compass quick-command facts suggest the Gradle build command only for
    repos that actually have a Gradle wrapper (_template_body's dead
    parameters removed; _mark_superseded's dead normalization store
    removed).
  • Comment/docstring sweep: history tags (task IDs, spec-section and
    audit-finding references, PR/version stories) removed across the source
    tree per the mandatory comment rules; init and build share one
    progress-rail callback helper.

[0.21.1] - 2026-09-21

Added

  • SCIP compiler-index support, redesigned as an opt-in edges-only overlay:
    a configured (or once auto-generated) .scip index contributes exact
    call/reference edges over the unchanged tree-sitter symbol graph —
    per-file authoritative, position-joined, per-document repo attribution,
    with a join-anomaly guard that retains tree-sitter edges observably.
    Seven-language run-once auto-generation registry (swift, java, kotlin via
    scip-java, typescript, python, go, rust) with scip_gen_* degradation
    records; cairn import-scip CLI and the scip config key; [scip]
    extra restoring the vendored protobuf stub; stats gains edge_sources
    and exact_share_by_language, build summary renders scip: n edges (d disagreements); cairn update re-applies the configured overlay after
    reindexing (import-only, never a generation); docs/scip.md toolchain
    guide.

Changed

  • Transitive closure rebuilt as an in-memory level loop with a single write
    pass (byte-identical row sets, ~2× faster on cairn's own repo) and now
    enforced by a CI budget gate — ≤60 s wall / ≤512 MB peak at the 1000-file
    scaling point with 5× structural edges (measured 6.0 s / 228 MB), plus a
    ≤2 s budget for one incremental maintain_transitive_closure call at
    the same point, whose reads are scoped to the affected subgraph.
  • edges table gains an additive source provenance column (NULL reads as
    tree-sitter); local commit gate's staged-files ruff honors
    extend-exclude via --force-exclude.

Fixed

  • Agent hook stdout stays protocol-clean: post_edit and session_end
    emit cairn/prose diagnostics to stderr instead of stdout (the calling
    agent parses Stop/PostToolUse hook stdout as JSON), and session_start
    routes digest subprocess failures to stderr so no error prose reaches
    the agent's context.
  • The SCIP import's write phase is atomic: every fallible read completes
    before the first delete, and the deletes/inserts run inside a savepoint
    that rolls back on failure — a mid-import error can no longer commit
    orphaned per-file deletes that permanently stripped tree-sitter
    calls/references edges from already-processed files.
  • SCIP local symbols resolve only against same-document definition sites
    (they are document-scoped by spec), instead of joining a same-named
    definition from an unrelated file.
  • _apply_scip_overlay logs a failed config read like every other
    degradation instead of returning silently.
  • cairn import-scip exits non-zero when no index document matches the
    graph (the workspace/db mismatch case that printed a normal-looking
    "0 edges" line), and the success line reports matched/skipped document
    counts.
  • scripts/regen_scip_pb2.sh splices the generated stub at the detected
    header end instead of a hardcoded line number that could disagree with
    the accepted header range.

[0.21.0] - 2026-09-17

Added

  • Bi-temporal memory: every memory carries valid_from/valid_until
    (additive memory_validity projection + concept extensions, one-time
    backfill); recall_memory/cairn memory search accept as-of dates;
    cairn validate-paths --mark expires stale-reference memories at build
    time with unique-successor links instead of deleting; cairn memory timeline <symbol> renders a symbol's memory history.
  • Multi-agent memory coordination: cairn memory share --agent <id> --symbols a,b,c publishes a learning to other agents' recall; cairn memory check warns on other agents' recent symbol activity (7-day
    window); search --agent / recall_memory(agent=...) merge shared
    entries with attribution; sharing unused keeps single-agent behavior
    byte-identical.
  • Security-aware blast radius: configurable taint sources/sinks (generic
    call-name defaults, cairn.json taint overrides), inter-procedural
    propagation over precise call edges (--fuzzy opt-in, depth-capped),
    cairn taint --from --to path tracing, and taint-path warnings in
    explore and cairn blast output.
  • Closed review loop: cairn review --base <ref> emits a structured
    review-context pack (blast radius + memories + compass + wiki);
    --pre-submit [--gate] warns on mistake/pattern memories keyed to the
    diff; --capture-event records resolved review comments as draft-tier
    symbol-keyed memories; advisory .github/workflows/review.yml posts
    findings as PR comments from a committed seed bundle.
  • cairn pack --task <text> --budget <tokens>: one-shot token-budgeted
    context block — semantic+lexical seed union, one-hop precise expansion,
    closure-gated centrality ranking, greedy budget fitting with per-class
    drop reporting, and per-symbol source/blast/compass/memory enrichment;
    deterministic, offline, no LLM. Bench pack-fit-rate arm: fit 1.00 at
    99.7% token reduction vs the grep baseline.
  • Cross-repo federation: federated_search MCP tool (inventory is now 25
    tools) and cairn federated-search merge per-store results with
    per-repo attribution and rank-level RRF fusion (lexical fallback for
    embedding-less stores, named dropped-store states); cairn ask --all-repos composes per-repo answers; opt-in --shared-embed serves
    stamp-compatible stores from one embedding backend.
  • cairn bench --suite swe-bench: deterministic (no-LLM) two-arm
    efficiency measurement over a pinned 50-instance SWE-bench_Lite subset
    (frozen manifest at a recorded HF revision; datasets rides the new
    bench extra); byte-identical reruns, hermetic -m core CI smoke;
    published medians: 84.6% tool-call / 99.0% token reduction vs the
    grep/read control, fresh-clone reproducible (resolve-rate intentionally
    out of scope for the deterministic arm).
  • VS Code extension (extensions/vscode/, vsix package): zero-config
    adopt-or-spawn of the local dashboard server, status item with graceful
    degradation (unindexed/stopped-server states), hover blast radius,
    caller/callee code-lens counts, and a compass/memory panel — over frozen
    editor JSON routes (/editor/symbol|file|status) on the dashboard app;
    tag-triggered extension-release.yml builds the vsix and validates
    install + activation on a headless clean machine.
  • Query-time freshness: every graph MCP tool and CLI query command probes
    (size, mtime) drift and reindexes changed files before answering;
    CAIRN_NO_REFRESH=1 / --no-refresh and read-only stores answer from the
    stored graph with a probe-derived staleness banner (no [watch] extra
    required).
  • cairn blast computes the reverse-edge blast radius of a git diff
    (working tree vs HEAD, or --base <ref> vs merge base) with
    text/markdown/mermaid/json renderers, precise edges by default (--fuzzy
    opt-in), innermost-symbol seeding by stored spans, and id-based traversal.
  • cairn map (CLI + MCP repo_map): deterministic repo orientation —
    per-repo directory clusters with counts, in-degree hubs, global hotspots,
    explicit dropped counts; zero LLM.
  • MCP file_api: signature-only symbol surface per file (kind, qualified
    name, synthesized signature, span; never bodies) with repo disambiguation.
  • cairn grep: regex search over indexed files grouped by innermost
    enclosing symbol, ranked by in-edge coupling, with -i/--fixed/--in
    and explicit truncation counts.
  • Wiki/compass pages preserve human-authored ## Notes sections
    byte-identically across regeneration; the critic does not evaluate Notes
    content.
  • cairn viz --export FILE.html writes a single self-contained HTML graph
    view (inline vis-network, no network assets).
  • cairn build --lsp: opt-in pyright stdio pass upgrades uniquely resolvable
    ambiguous Python edges to exact; missing/failing server is a no-op with a
    notice; existing exact edges are never downgraded.
  • Rust (.rs) indexing via a generic tree-sitter tier (pinned
    tree-sitter-rust); call edges stay unresolved (never exact).
  • discover_repos optionally includes initialized submodules and nested
    repos (include_nested_repos, default off, prefixed repo ids); linked git
    worktrees seed an independent store from the main checkout then refresh
    drift.
  • cairn dataflow build --max-symbols N caps the public symbols indexed per
    run; CAIRN_DATAFLOW_MAX_SYMBOLS sets the cap for every builder entry point
    (cairn build, cairn sync, cairn dataflow build). The partial-index
    warning names the env var and flag.

Changed

  • The [ingest] extra pins onnxruntime<1.24 on Python 3.10 so
    pymupdf-layout resolves to an installable wheel set.
  • Dataflow builds upsert rows in batches and compute the repo-keyed
    cross_repo_deps payload once per repo per run instead of once per symbol.
  • CI test legs run the suite under pytest-xdist (-n auto --durations=25);
    PR legs run the Python 3.14 ceiling only — the full 3.10-3.14 matrix still
    gates every push to main. Eval-path tests pin the dep-free hash embedder,
    keeping the semantic model out of the retrieval loop.
  • CI installs every job from the committed uv.lock via uv sync (the
    lockfile is the tested dependency set, not a fresh pip resolution), runs
    the full 3.10-3.14 matrix on merge-group entries as well as main pushes
    (merge-queue-ready), and sets per-job timeout-minutes.

[0.20.2] - 2026-09-13

Added

  • cairn stats reports edge resolution shares (exact / ambiguous /
    unresolved) over the calls/references candidate pool.
  • cairn update embeds changed files' symbols when a semantic backend is
    reachable, and reports deferred embeds when none is available.
  • cairn.json committed at the repo root excludes vendored dashboard static
    chunks and benchmark datasource corpora from indexing.

Changed

  • cairn embed builds the multivector kinds (name, docstring) by
    default; --no-multivector restores single-vector builds.
  • reindex_paths settles embed_symbols' buffered-batch degradation, keeping
    an open transaction from failing subsequent file legs or losing rows.
  • Shared graph helpers (_select_stale_symbols, _purge_embedding_rows,
    _repo_relative_path) replace duplicated logic across the embedding and
    incremental paths.

[0.20.1] - 2026-09-11

Removed

  • The SCIP compiler-index ingestion path: cairn import-scip, the
    cairn.json scip key, the [scip] extra (protobuf runtime), the
    vendored protobuf stub with its regeneration script, and the external
    indexer auto-generation orchestrator. Indexing is tree-sitter-only for
    every language; a stale scip key in cairn.json is ignored like any
    other unknown key.

Added

  • Parser-emitted binding signals for all fourteen languages: import
    aliases (imports.local_alias), definition arity (symbols.arity),
    call arity, and receiver types for the nine parsers that previously
    emitted none (python, c/cpp, csharp, dart, js/ts, objc, swift). The
    resolver consumes them: a bare call through a local import alias
    resolves exact, and same-named overloads split by argument count when
    exactly one arity matches. Receiver-typed lookups that collide across
    same-named types now narrow by same-file and fall through the tier walk
    instead of degrading to ambiguous. Intra-workspace exact share rises on
    the measured corpora (self 0.2248 → 0.2429; ds2/attrs 0.6546 → 0.6781)
    with retrieval ground truth unchanged (558/558 + 234/234).

[0.20.0] - 2026-09-10

Added

  • The dashboard graph tab draws the whole picture instead of an empty
    canvas whenever a scope's filter is blank:
    • symbol and impact scopes with no focal name render the
      workspace overview — the most-connected symbols on one canvas,
      ranked by degree (fan-in + fan-out), capped at 50 symbols and 100
      edges, tests excluded by default with the Include-tests toggle
      opting back in, vendored/minified assets never candidates, and
      edges deduplicated on (source, target, kind). Metadata reports
      tests_included, truncated, and vendored_excluded so a
      count at the cap is visibly truncated. A focal name still draws
      that symbol's neighborhood; a name that matches nothing still
      renders the empty state.
    • The repo scope with no repo id buckets every repo in the store,
      capped at 30 buckets with an honest truncated flag in metadata.
  • The MCP visualize_graph tool rides the same viz layer: module
    diagrams include their real internal edges, and format="json"
    output for the repo scope carries the truncated field.
  • The dashboard sidebar's Database item has an icon (hard-drive glyph)
    like every other nav view; a source-pinned test fails if a future
    nav view ships without a glyph.

Fixed

  • The dashboard's workspace selector names the launch workspace (e.g.
    polaris (launch)). The selector's no-selection option serves the
    launch store — the dashboard process's own db — so the label shows
    which workspace every view queries by default. The generic
    Launch workspace label applies only to custom --db paths outside
    the store registry.
  • Module- and symbol-scope edge queries apply the kept-node target
    predicate in SQL before the 100-edge cap, so the cap lands on real
    internal edges and the rendered edge set is stable across scans.

[0.19.1] - 2026-09-10

Added

  • Relationship-kind enrichment across extraction and display. The graph's
    canonical edge-kind vocabulary is now calls, extends, implements,
    embeds, with, references, decorates, imports, contains.
    • Python base classes emit extends (was implements); decorators emit
      decorates; signature type annotations emit references. Swift splits
      a class's first inheritance target to extends (the superclass) from
      protocol conformances (implements). Go struct/interface embedding
      emits embeds. Kotlin type annotations emit references.
    • Every build synthesizes one module symbol per file (dotted
      repo-relative qualified name), derives contains edges for nesting
      (module → top-level, parent → nested), gives module-level code an edge
      owner, and materializes imports module-to-module edges from the
      imports table by longest-suffix path matching (ambiguous and external
      imports are skipped). Incremental syncs refresh the affected repo's
      imports edges.
    • The dashboard graph canvas colors and dash-patterns edges by kind from
      new --edge-* theme tokens, renders an edge-kind legend whose chips
      filter whole kinds, and the symbol/neighbors/inspect views pass the
      stored edge kind through verbatim (previously every edge rendered and
      labeled as calls).
    • Resolution semantics preserved: module symbols are excluded from the
      resolver's candidate indexes (a file named after its single class
      would otherwise make same-name references ambiguous), embeds joins
      extends/implements in the receiver-dispatch ancestor index, and
      the new kinds stay outside STRUCTURAL_EDGE_KINDS so impact/trace
      radius is unchanged. Requires a rebuild (cairn build) to take
      effect. The DS-v2 ground-truth seal's recorded per-corpus build
      facts are re-pinned to the new deterministic counts (558/558
      expectations and tree_hash pins unchanged).

Fixed

  • The doc-link completion gate now confines proposed edges to the
    completing task's island members: a result naming an existing doc
    outside facts.members is rejected with no writes (the task stays
    in-progress and re-completable), instead of writing kind: inferred
    edges between arbitrary knowledge docs.

[0.19.0] - 2026-09-09

Added

  • The dashboard gained a /knowledge catalog over the ingested
    knowledge docs: every stored doc lists exactly once with its family
    (decision / spec / workflow / business-rule), status, related-doc
    count (from the derived knowledge_edges index), tags, and last
    update. Family/status/tag filters morph-swap the results region as
    htmx fragments (no page reload), rows link wholesale to
    /knowledge/{family}/{slug} — a detail page rendering the doc's
    title, badges, and markdown body (relationship panel follows in the
    next knowledge view) — an empty workspace renders an explicit
    empty state naming cairn knowledge ingest, and a store predating
    the relationship index still catalogs with zero link counts. The
    sidebar's Knowledge section leads with the catalog entry, the landing
    grid carries a Knowledge card, and the command palette offers both
    the catalog and a palette-only Knowledge Graph destination.
  • The /knowledge/{family}/{slug} detail page gained the relationship
    surfaces: a supersede chain widget (ordered oldest -> newest, each
    member linking its detail page, the hop labeled with its relation and
    a direction arrow, the viewed doc highlighted), a relationship panel
    grouping every stored neighbor row under its relation with a kind
    badge (extracted / inferred / derived — the same tuples
    cairn knowledge related prints; inferred wears a dashed badge), an
    explicit empty state for docs with no relationships, linked code refs
    with their kind and per-ref resolution status (symbols deep-link into
    the graph), and ingest provenance — source repo/path and the doc's row
    in the workspace's staged .cairn/ingest-outbox/manifest.json, or an
    explicit note when a doc was added directly to the store. A store
    predating the relationship index renders empty panels, never an error.
  • The dashboard gained the /knowledge/graph relationship canvas (the
    palette's Knowledge Graph destination, linked from the catalog):
    every stored doc is a node and every indexed relationship a directed
    edge drawn exactly as stored — a supersede pair reads as the two-way
    link ("supersedes" on the newer doc, "superseded-by" on the older)
    the related CLI and the detail panels report, with edges carrying
    their relation + kind so inferred links render dashed and
    extracted/derived links solid. The legend doubles as the filter:
    kind/relation chips toggle whole edge groups client-side (no reload,
    no refetch), the counts line tracks the visible share, and a node
    click fetches the doc's inspect fragment (/knowledge/graph/inspect)
    into the side panel — identity, relationships with kind badges, and
    the full-detail link. The canvas re-themes live via the shell's
    cairn:theme-changed broadcast and drops physics under
    prefers-reduced-motion: reduce (the /graph machinery's conventions).
  • Vendored Mermaid 11.17.2 (mermaid.min.js, MIT) as the dashboard's last
    external asset: the wiki detail view now loads it from /static/ via a
    lazy dynamic import() that ships only on pages holding a mermaid fence
    (replacing the jsDelivr CDN import), so fence-free wiki pages make no
    mermaid request and the dashboard is fully offline at runtime. Fences
    render explicitly via mermaid.run() after the late import (startOnLoad
    off), with the code block kept as the no-JS/unavailable fallback.
  • The dashboard serves its own icon: the shell head links the vendored
    favicon.svg, and the conventional /favicon.ico path serves the same
    file, so the browser's automatic icon probe is a 200 instead of a 404
    on every page load.
  • The /graph symbol inspect panel loads as an htmx fragment: the
    node-click fetch rides htmx.ajax (the HX-Request header keys the
    route's fragment branch) and swaps the server-rendered panel —
    identity, callers, callees, impact with affected tests, neighbor rows
    deep-linking into the symbol-focused view — into #graph-panel with
    no page navigation (sidebar/topbar persist by node identity). A stale
    response from an earlier selection is cancelled at htmx:beforeSwap;
    requests without the header keep the JSON payload. The
    /knowledge/graph inspect fetch rides the same htmx.ajax seam.

Changed

  • The graph and database canvases re-theme live through CSS variables.
    The per-kind node palette moved from hardcoded JS color tables into the
    theme blocks as --kind-* tokens, and the shell broadcasts a single
    cairn:theme-changed event on a theme flip (one data-theme observer
    app-wide, in shell.js); both canvas scripts re-read every color through
    the getComputedStyle token proxy on that event, so toggling the theme
    re-colors the live canvas in place — no reload, no new graph requests.
  • The /graph physics simulation is disabled under
    prefers-reduced-motion: reduce — JS-driven motion the CSS media
    query cannot reach. The graph then lays out on the deterministic
    spiral (a static constellation, no stabilization animation), and the
    canvas zoom/fit controls skip their eased animation.
  • The dashboard's list views filter and live-refresh through htmx
    fragments. Filter forms on /history, /tasks, and /wiki are htmx
    triggers (input changed delay:300ms; the tasks select on change)
    that morph-swap only the results region — no page navigation, the URL
    and in-progress filter input are untouched, and the form lives outside
    the swapped region so a swap can never destroy a field. The
    #refresh-region live refresh on /history, /tokens, and /chains
    is now an htmx poll (hx-trigger="every 5s"): each cycle re-fetches
    the view's HX-Request branch as a cheap region fragment and
    alpine-morphs it over the region in place, replacing the old
    full-page re-fetch/DOMParse swap; the live chrome (state word, pause
    toggle, disconnect banner) is driven from the htmx event lifecycle —
    pause refuses the poll's request via htmx:beforeRequest while the
    timer keeps its schedule, and window scroll is re-anchored around each
    swap. The same routes serve full page or fragment from one handler
    via the HX-Request header. The command palette is a native
    <dialog> element driven by an Alpine
    component: showModal() provides the focus trap and Esc-to-close, and
    focus returns to the launching element on every close path. Rows come
    from the same two sources as before — the server-rendered seed JSON
    draws the initial list, and typing fetches filtered rows as an htmx
    fragment from the new /palette/results route (the same view/workspace
    seed composition plus the /graph/suggest symbol data function, with
    the selected store riding the request). Keyboard behavior is
    unchanged: Ctrl/Cmd+K toggles, arrows move the active row, Enter
    navigates.
  • Accessibility pass over the dashboard shell and views. Keyboard focus
    is always visible: the global :focus-visible ring (2px accent
    outline) stands everywhere, the shared filter-bar inputs keep the ring
    alongside their focus tint, the palette search input carries an inset
    ring (the card clips an outward one) plus an aria-label, and no
    stylesheet rule removes the focus outline. Every icon-only button
    exposes a non-empty accessible name (theme toggle and sidebar collapse
    already flip their labels with state), pinned by a hermetic sweep over
    all fourteen main views. prefers-reduced-motion: reduce collapses
    motion to ~zero — transitions/animations run at 0s, smooth scrolling
    stops, the live-refresh pulse stops outright — while toggles, palette,
    and htmx swaps behave as before. The shared table macro's real table
    semantics (thead/tbody, <th scope="col">) are pinned by test.

Added

  • Vendored the dashboard's interaction stack as static assets (zero CDN,
    zero build step): htmx 2.0.10 (htmx.min.js, 0BSD), Alpine.js 3.17.1
    (alpine.min.js, deferred, MIT), its morph plugin (alpinejs-morph.min.js,
    MIT), and the alpine-morph htmx extension (alpine-morph.js). The shell
    loads all four from /static/ with the ?v= cache-buster, runs htmx with
    allowEval disabled via the htmx-config meta, and ships the
    [x-cloak] { display: none } rule so Alpine-managed DOM never flashes
    pre-init. Route handlers gain the is_hx_request seam (the HX-Request
    header) for serving full pages vs fragments from one route.

Added

  • cairn knowledge list --json emits one machine-readable row per
    document — concept_id (bare bundle-relative id), title, doc_type,
    doc_status — so callers and validators can pin document ids without
    search --json or sqlite. Human output and the --type/--status/
    --tag filters are unchanged.
  • Dangling relationship pointers are surfaced instead of silently
    skipped: a declared relates_to pointer matching neither a knowledge
    concept id nor a recorded resource path warns by name — in the ingest
    dry-run output (before any write, checked against the store plus the
    staged run), in knowledge ingest --ingest output (from its post-write
    index rebuild), and in cairn knowledge rebuild output. The pointer
    stays in the frontmatter (the durable record) and still never reaches
    knowledge_edges; relationship-free documents ingest and embed exactly
    as before, and the knowledge ingest flag set and the 22-tool MCP
    surface are unchanged.
  • Island detection behind the LLM task queue: connected components over the
    knowledge_edges doc graph surface pair-units detached from the corpus —
    two-doc components and id-ordered singleton pairs — and queue one
    doc-link task per pair via knowledge ingest --ingest and cairn knowledge rebuild; task facts carry the member concept_ids and titles,
    and queueing is deduped per member set so re-runs never duplicate tasks.
    Completing a doc-link task through cairn task claim/task complete --result-file is gated by a deterministic critic that verifies every
    proposed edge references existing knowledge concept_ids. A valid result
    is written back as kind: inferred relates_to entries on both docs'
    frontmatter (supersedes mirrors to superseded-by) and, through the
    index rebuild, as kind: inferred knowledge_edges rows. A completion
    referencing a nonexistent concept_id is rejected with no writes: the task
    stays in-progress and re-completable, and the rejection names the invalid
    reference. New cairn knowledge islands verb lists islands and their
    task state (read-only).
  • Stored-edge search expansion: search_knowledge's related-doc expansion
    reads the knowledge_edges relationship index instead of recomputing
    tag/module overlap at query time. Neighbors connected by extracted or
    derived edges surface with a >=50%-of-parent-score boost (both edge
    directions, one entry per neighbor, still respecting each doc's own
    lexical hits and visibility); inferred edges never boost — low-trust
    links do not elevate a doc beyond its own merit. Stores without a rebuilt
    index simply get no expansion.
  • Doc-to-code reference verification at ingest: backticked file paths and
    symbols in a staged document's body resolve against the L1 graph via
    cairn.refs (file_exists/symbol_exists, the wiki verified-sources
    pattern); only resolvable refs are stored — as {ref, kind: file|symbol, verified: true} entries in the promoted concept's OKF verified family
    and, through the post-ingest rebuild, as knowledge_doc_refs rows. Bogus
    refs appear nowhere; docs without resolvable refs gain no verified
    family. add_document gains an optional verified_refs parameter and the
    ingest report/CLI output carries a verified_refs count.
  • Derived knowledge-relationship index: knowledge_edges(doc_id, related_id, relation, kind, provenance, created_at) and knowledge_doc_refs(doc_id, ref, ref_kind, verified) tables (additive, idempotent schema) rebuilt from
    the OKF bundle by cairn knowledge rebuild and automatically after
    knowledge ingest --ingest. Declared relates_to pointers index as their
    declared relation/kind (bare concept-id, path-shaped, and source-path
    pointers all resolve; unresolved pointers stay frontmatter-only); doc pairs
    sharing tags or affects_modules materialize symmetric kind: derived
    edges; sources/verified entries with a ref become knowledge_doc_refs
    rows. Rebuilds are idempotent — unchanged bundles reproduce identical table
    contents including created_at stamps. normalize_doc_id (in
    knowledge/store.py) is the single id-normalization point for the rebuild
    and later CLI/dashboard consumers.
  • ADR supersede-chain detection at ingest: decisions//adr/ documents
    with NNNN- numbered filenames gain supersedes / superseded-by
    relationship entries from body or status-line markers ("Supersedes
    ADR-0001") with no explicit frontmatter; both directions are written
    (new doc supersedes, old doc superseded-by), resolution stays within
    one ingest run and directory family, and unresolved or self-referential
    markers are dropped rather than staged as dangling pointers.
  • Author-declared document relationships survive ingest: the source parser
    keeps unknown frontmatter keys on ParsedDoc.extensions instead of
    discarding them; relates_to/supersedes/superseded-by frontmatter
    normalizes into a relates_to extension ({concept_id, relation, kind},
    kind defaulting to extracted) that rides the staged OKF file, the
    dry-run manifest row, and the promoted .knowledge frontmatter;
    add_document gains an optional relationships parameter; dry-run
    cairn knowledge ingest output lists each staged doc's relationships
    before --ingest writes anything.
  • Tribal-memory loop closure: explore surfaces a tribal-memory section
    (≤3 entries, title + "How to apply" line) and records memory_refs rows
    for rendered memories; session_start hook emits the top tribal-memory
    digest once per session; post_tool_failure captures mistake memories on
    the second occurrence of a normalized failure signature
    (memory_failure_signatures table + cairn memory record --recurrence-key) and joins the installed hook set; capture_memory
    force-routes session-bookkeeping-shaped content to the 7-day raw tier;
    session_end reads the Claude Code transcript_path JSONL and queues
    memory-extract (the capture path was previously unreachable); memory
    scoring drops the constant critic_score/authority terms and
    renormalizes the five live signals; new L4 eval level (recall@k/MRR for
    search_memory) and a cairn doctor memory_staleness check WARNing on
    write-only tribal memory; six memory lifecycle tools leave the MCP
    surface (28 → 22 tools) remaining available via cairn memory CLI verbs
    (cairn memory demote gains --db).

Changed

  • Dashboard views restyled onto the token system end to end: every view's
    repeated markup renders through shared Jinja macro libraries
    (_table.html data tables + empty states, _filters.html filter forms
    carrying the hidden store field, _cards.html stat tiles + titled
    cards), so table, filter-row, and summary-tile markup is identical
    across views, and the legacy graph-controls form class is retired for
    filter-bar. The pre-token alias vars are deleted: templates, the
    scripts' cssVar() reads, and component CSS resolve the ladder names
    (--bg-0..3, --line-1/2, --text-1..4, --accent-hover) directly —
    inputs, chips, and floating controls sit on --bg-2 over --bg-1
    panels. Controls reach final density — --control-h 30px with 13px
    text, 1.2 line-heights, and matched paddings on filter inputs, selects,
    submit buttons, the workspace selector, the palette button, and the
    live pause pill — and data tables densify to ~33px rows at the 13px
    base. The chains view's inline style block moves into the stylesheet
    under the same tokens.
  • Dashboard design tokens, dark-first: the stylesheet's theme layer is a
    near-black surface ladder (--bg-0 #08090a … --bg-3 #18191a), 1px
    hairlines (--line-1/2) instead of drop shadows (panels, dropdowns, and
    the topbar carry no box-shadow — elevation is a surface step plus a
    hairline), a text ramp (--text-1..4), one indigo accent
    (--accent #5e6ad2, --accent-hover), and status colors (--ok/warn/err)
    in the first variable block, with [data-theme="light"] carrying the
    light ladder (#ffffff page, #f7f8f8/#f2f3f4/#ececee surfaces,
    #17181a text) and [data-theme="dark"] restating the dark defaults;
    both theme blocks redefine every token and set color-scheme. Constants
    gain spacing (4/8/12/16/24), radius (4/6/10/14), and 13px-base type
    scales on the system font stacks. Theme resolution is unchanged: the
    inline pre-paint script still resolves stored choice → OS preference →
    dark, and no prefers-color-scheme media query exists in CSS.
    Pre-token component var names (--bg, --surface, --border, …) map
    onto the ladder until views restyle onto the token names directly. The
    theme test pins the new contract: first block holds the dark tokens and
    nothing else, both theme blocks redefine every token, no system-color-
    scheme media query.
  • Wiki two-kind contract: the wiki's two stored kinds have disjoint,
    structurally separated purposes — the plan manifest records pipeline
    intent only (identity, seeds, input hash, task linkage, queue attempts;
    schema cairn-wiki-manifest-3 with v2/v1 manifests migrating on load),
    while promoted Wiki-Article concepts remain the sole record of what
    exists (body, sources, provenance sha/task id). All lifecycle state is
    derived at read time by the new cairn.wiki.lifecycle module (one
    promoted-check implementation instead of five), the dashboard and CLI
    expose derived state only, staleness never verdicts on pages without
    content, completions write done only after the critic verdict (a
    critic failure leaves the task re-completable — no more queued-forever
    zombies), wiki-page tasks are keyed by the qualified {repo}/{page_id}
    resource (repos can share page ids), enrichment no longer blocks
    generation or snapshots page bodies into task facts, and the ungated
    deterministic reports left the wiki search surface (renamed
    Architecture-Report under reports/architecture/). The wiki is the
    agent-facing knowledge surface for the whole workspace — code or
    documents — searchable via search_knowledge/ask_compass and
    explorable in the dashboard. First generate after upgrade re-queues all
    pages once (the input-hash recipe now covers source and document
    seeds); in-flight legacy tasks complete normally.

Fixed

  • The /history tool filter narrows rows again: the filter matched the
    stored tool_name column exactly, but CLI rows are stored namespaced
    (cli:<command_path>, e.g. cli:cairn build), so every non-empty
    value a user types — including the bare family (cli) — matched
    nothing and rendered "No matching calls" while the session filter
    worked. The filter is now a literal prefix match on the stored value
    (exact = the whole value), so both the family and the full stored name
    narrow rows, wildcard characters (%, _) in a typed value match
    literally, and the .csv/.json exports ride the same semantics.
    No-match values still render the empty state, never an error.
  • The settings page annotates its numeric knobs (timeout, server batch)
    with "leave empty to keep the current value", making the blank-submit
    no-change behavior visible instead of silent.
  • The suite-wide hermetic test fixture re-points cairn.paths' import-time
    stores-root bindings (CAIRN_HOME, REGISTRY_FILE, SHARED_LIB along
    with the already-patched CONFIG_FILE) into the per-test sandbox. paths
    consumers read these module attributes at call time, so unpatched, tests
    running on a developer machine rendered the real ~/.cairn stores into
    dashboard pages (store switcher, /workspaces) and a workspace
    registration wrote the real registry; renders and writes now enumerate
    and land only in the test sandbox. A regression test pins the contract:
    rendered dashboard pages carry exactly the sandbox's store keys.
  • relates_to basename pointers resolve only a UNIQUE resource match: a
    pointer like target.md whose basename matches several promoted
    resources (docs/target.md, archive/target.md, …) previously
    resolved to the single shortest one, indexing a wrong-but-existing
    edge with no warning. Now nothing is picked — the pointer indexes
    nothing and surfaces through the dangling-warning channel (ingest
    dry-run, knowledge ingest --ingest, cairn knowledge rebuild) as an
    ambiguous pointer naming the candidate paths. Unique-basename
    pointers, exact resource paths, and concept-id pointers resolve
    exactly as before.
  • The knowledge-ingest count verify leg compares the post-write store
    against the full expected population (pre_existing + accepted - overwritten, with add_document writing over existing docs in
    place), so an incremental knowledge ingest --ingest into a non-empty
    store — new documents added, or the same documents re-ingested —
    verifies and exits 0 exactly like a first-into-empty run. A dropped
    write still fails the leg, and standalone pre-write verify_manifest
    use keeps comparing the batch alone against the store as it stands.
  • Re-ingesting a document preserves its promoted kind: inferred
    relates_to entries: the ingest executor merges the existing
    frontmatter's critic-approved doc-link entries into the rewritten
    concept (declared source entries first, deduped on
    (concept_id, relation, kind)), so the durable record — and the
    rebuilt knowledge_edges rows — keep approved LLM links across
    re-scans.
  • relates_to pointer resolution tries resource-prefixed forms: fed
    documents promote workspace/<relpath> resources, so a bare
    repo-relative pointer (e.g. docs/adr-0001-postgres.md or a sibling
    fed file's name) resolves through its prefixed resource and indexes as
    a declared edge — in the rebuild and in the ingest dry-run alike —
    instead of dangling silently; genuinely unresolvable pointers still
    warn by name.
  • CLI (cairn dataflow lookup): the subcommand registers under its documented
    name (Click had derived dataflow-lookup from the function name, so the
    documented cairn dataflow lookup form was not invokable); the shipped
    impact_guard.py skill helper and its eval rule call the corrected name.
  • Agent install (droid): droid mcp add matches the documented stdio
    shape (the full server command as one argument, --type stdio) and
    pins a non-default home via the CLI's --env flag (replacing the
    "no verified env mechanism" warning). A non-zero CLI exit is reported
    as a WARNING naming the exit code and stderr, and falls back to the
    .factory/mcp.json file registration; droid mcp remove reports
    non-zero exits instead of staying silent. The user-scope
    registration file (~/.factory/mcp.json) is now read by
    check_installed and the doctor's registration audit, matching the
    other clients' global configs.
  • Agent install (install-agents --scope global, Claude Code): the
    claude mcp add registration places -e entries before a --
    separator so server commands carrying flags (the
    python -m cairn.cli.main fallback) register instead of being
    rejected as unknown options, and a non-zero CLI exit is reported as a
    WARNING naming the exit code and stderr instead of a success note.
    The user-scope registration file (~/.claude.json) is now read by
    check_installed and the doctor's registration audit (env-staleness
    check + spawn probe), matching the other clients' global configs.
  • Server boot (cairn serve / MCP stdio): non-actionable third-party
    output suppressed — HF Hub unauthenticated-request warnings, MCP
    pre-initialization request warnings, model-loading progress bars.
    Errors still surface.
  • Compass critic file refs (refs.file_exists):
    • directory paths resolve (app/adk): a directory is valid iff an
      indexed file lives under it
    • repo-qualified refs (repo/app/adk) re-validate within that repo
    • LIKE wildcards in refs are escaped
    • inherited by every consumer (critic, memory scoring, wiki
      sources/refine, task gate, workflow validation)
  • Compass module resolution (multi-repo workspaces):
    • module matching is segment-anchored; no substring matches
    • bare names resolve via the repos containing them; ambiguity raises a
      --repo hint instead of mixing repos
    • repo/module paths normalize to repo-relative; all generation paths
      (deterministic, LLM, task queue) produce one concept identity
    • cross-dep sources are repo-scoped; a same-named directory in another
      repo is a cross-module target (repo-qualified label)
  • Dashboard /database view:
    • sqlite-vec internals (vec_*, vecmv_*) are excluded
    • unintrospectable tables are skipped and reported (banner), never
      fatal
  • Knowledge relationships hardening:
    • the knowledge_doc_refs rebuild dedupes on its (doc_id, ref, ref_kind) primary key keeping verified=max, so a ref carried by
      both the sources and verified families with differing flags
      indexes once instead of crashing the rebuild (which runs after
      every knowledge ingest --ingest)
    • a doc-link completion proposing zero edges is rejected ("no
      proposed edges found") with the task left in-progress and
      re-completable; empty/heading-only results can no longer promote
      vacuously and permanently cover an island
    • ingest staging accepts any Iterable of entries (generators
      included) and the manifest serializes non-JSON extras (e.g. YAML
      date values) via default=str instead of crashing the dry run
    • supersede_chain documents its linear-chain assumption and asserts
      the queried doc's membership in the result
  • Built wheels ship the dashboard's whole asset tree: the package-data
    glob for static/ is recursive (static/**/*), so nested asset
    subtrees reach wheel installs — a flat static/* omitted every file
    below the top level (the vendored mermaid chunk tree among them), and
    on a wheel install each chunk import 404'd, silently degrading wiki
    fences to code blocks. A packaging-coverage test replays setuptools'
    glob matching over templates/ and static/, so a data file the
    globs miss fails the suite instead of surfacing as a runtime 404 on
    installs.
  • /history polls hold the active page: the polled region bakes its
    before/after cursor into the poll URL at render time, so a
    cursor-paged slice re-renders itself across the 5s poll cycles instead
    of silently reverting to page 1 (filters still ride the form's live
    fields, and a filter change still resets paging).
  • htmx back/forward history restores (HX-History-Restore-Request)
    render the full page rather than a region fragment: htmx stamps
    HX-Request on restores too, so every fragment-branched route would
    otherwise serve a bare region to a restore once any view adopts
    hx-push-url. The shell's script-order test also pins the full load
    sequence — htmx core, alpine-morph extension, morph plugin, Alpine
    core.
  • /favicon.ico answers a missing vendored icon with 404 instead of an
    unhandled 500.
  • cairn build exits non-zero when it produces no store. A build
    whose persist phase had nothing to write (a workspace with no
    indexable files) left the db's parent directory uncreated, the
    derived-index open then failed with store parent directory does not exist, and the failure surfaced only as a dataflow skipped note in
    the summary panel while the command still exited 0 — scripting keying
    on the exit code for "the store exists" saw a silent skip. The
    no-store outcome now fails the command with a named error and exit 1;
    a build over an openable store is unchanged.
  • /knowledge out-of-vocabulary filter values fall back to no filter,
    matching the /tasks /memory /wiki convention (and the route's
    documented contract): a ?family=/?status= value the selects can't
    offer previously rendered a filter-empty results region while the
    select read all. The vocabulary is the classifier's families / the
    doc statuses, each unioned with what the corpus actually contains, so
    a doc under a custom type stays reachable from the filter.
  • The /knowledge archived status badge renders in the warn palette
    like its sibling statuses instead of the unstyled base badge, and the
    doc-detail provenance payload drops an origin key no template read.
  • The graph canvases' inspect fetches abort their superseded
    predecessors (htmx:beforeSend carries the live xhr) instead of
    letting stale responses complete in the background, and the dead
    htmx:timeout listeners are gone — htmx fires that event only with a
    configured timeout, which no request in the dashboard sets.
  • A wiki manifest whose pages section is not a mapping (e.g. a list
    from an external edit) fails with a clear ValueError naming the
    expected {repo}/{page_id}-keyed mapping instead of a bare
    AttributeError from row normalization. The wiki commands that read
    the manifest (status, retry, export, enrich, and
    generate --llm) report it as unopenable — one stderr line naming the
    problem, exit 1, no traceback — and the dashboard's /wiki catalog
    and page views render an explicit unreadable-manifest state naming
    the problem instead of a 500.

[0.18.0] - 2026-09-02

Added

  • Dashboard database view (/database, System section): the store's
    schema rendered as an interactive relationship graph on the same
    vis-network canvas as the Graph view — tables are nodes sized by row
    count, solid edges are declared foreign keys, dashed edges are
    *_id references implied by column naming (a column already covered
    by a declared FK is never re-inferred), internal sqlite_%/FTS shadow
    tables are excluded, and selecting a node opens a side panel with the
    table's columns, types, and incoming/outgoing references. Theme flips
    re-color the live network.
  • Dashboard Memory view categorization: a Type filter pill row
    (all · decision · pattern · mistake · workaround) above the table,
    mirroring the Wiki catalog's state filter — ?type= narrows the list
    server-side before the newest-first limit applies, an
    out-of-vocabulary value silently falls back to the unfiltered page,
    the active pill renders highlighted, and the empty state names the
    category when a filtered type has no records.

Fixed

  • Dashboard alignment: every .graph-controls row (graph filter bar,
    settings embedding config, history/tasks/wiki filters) now renders on
    one baseline — selects, text/password/number inputs, and submit buttons
    share a fixed control height (their differing intrinsic widget heights
    previously sat raggedly under the rows' bottom alignment), checkbox
    toggles render as one centered line instead of stacking the checkbox
    above its caption, and inline status chips (env-pin markers, the API-key
    state) meet the same bottom line as the controls they annotate. Data
    tables no longer collapse under width pressure: timestamp cells hold
    one line instead of wrapping mid-string at their hyphens, and long-ID
    code chips (memory IDs, history sessions, task IDs/resources) cap to
    one ellipsized line with the full value on the title tooltip instead
    of being squeezed to a one-glyph sliver by the auto table layout.

Changed

  • Dashboard sidebar: the collapse control and theme toggle are icon-only
    square buttons — a chevron that flips with the collapse state, and a
    sun/moon glyph showing the theme the button switches to (sun while
    dark, moon while light). The visible text is gone; the accessible
    name and tooltip state the action ("Switch to light theme" /
    "Collapse sidebar") and stay in sync in both states.

[0.17.0] - 2026-09-02

Added

  • Dashboard shell: a global workspace selector in the topbar of every view
    (server-rendered from the store registry, populated stores only, labeled
    by workspace path) — switching rewrites the ?store= param on the
    current URL and reloads, so the tab stays and every view re-renders on
    the selected workspace, with the existing localStorage stickiness
    re-persisting the choice; a Ctrl/Cmd+K command palette searching
    views, workspaces, and live symbol suggestions through the same
    /graph/suggest endpoint the graph typeahead uses; and a collapsible
    sidebar (persisted pre-paint like the theme) with the nav grouped
    Explore / Knowledge / Activity / System — Workspaces still leads
    ungrouped — rendered from one NAV_SECTIONS source shared with the
    palette so the two can never drift.

  • Dashboard wiki navigation: canonical repo-qualified page URLs
    (/wiki/{repo}/{page_id}) fix the multi-repo collision where every
    repo's overview page but one was unreachable — the old one-segment
    URL survives as a 307 redirect carrying the selected store. The catalog
    groups pages by repo with state-filter pills and a search box
    (title/page-id/description); detail pages carry a breadcrumb, an
    in-page table of contents (heading anchors emitted by the renderer,
    h2/h3 outline provably matching them), prev/next links walking the
    repo's promoted pages in plan order, and graph cross-links — inline
    markdown links render under a strict scheme allowlist
    (http(s)://, /, #; javascript:/data: stay literal text) and
    backticked refs the page itself vouches for (frontmatter sources plus
    plan seeds, both already graph-verified by the critic gate) link into
    /graph?scope=symbol&focus=… with the selected store riding.

Changed

  • Dashboard visual redesign ("Terrain"): the GitNexus teal tokens give
    way to a warm sandstone light mode, deep ink-brown dark mode, and a
    burnt-amber primary — same token names in both theme blocks, so the
    pinned theme contract holds unchanged. Component pass: transparent
    table headers with a strong rule, monospace code chips, the active nav
    item carries an accent inset bar, and the new wiki surfaces (catalog,
    article prose at a 46rem measure, TOC rail, pager) get their styles.

Fixed

  • Dashboard forms silently dropped the selected workspace: GET filter
    forms (history/tasks/graph) lose the action URL's query string on
    submit and the settings POST actions carried no store at all, so
    applying a filter or saving settings on a selected store reverted to
    the launch store one hop later — and for the POSTs, the stickiness
    redirect could land back on a POST-only route (405). Every form now
    carries a hidden store input and settings_save resolves the body
    store through the same validation seam.

[0.16.3] - 2026-09-02

Added

  • Wiki trust context and outward surfaces: every promoted wiki page now
    records the workspace HEAD commit sha it was generated at (in the
    concept's extensions and the manifest row), and both cairn wiki status
    and the dashboard wiki views compare it with the repo's current HEAD —
    fresh when they match, stale when both are present and differ,
    unknown when either side is unavailable — so "how old is this page" is
    answerable at a glance. cairn wiki export --dir DIR [--force] writes
    every promoted page as DIR/{repo}/{page_id}.md with its OKF frontmatter
    preserved (a non-empty target directory is refused without --force) and
    reports the exported count, and cairn wiki enrich [<page-id>|--all]
    queues wiki-page-enrich tasks whose facts carry the page's current body
    plus fresh seeds; a critic-passing completion appends the new sections to
    the promoted body — the prior text stays in the page — and merges the new
    ## Sources entries into the frontmatter (old entries first, deduped),
    riding the existing critic gate and bounded revise cycle. Enrichment
    requires an already-promoted page.
  • Queue maintenance without store surgery: cairn task drop <id> marks a
    pending or in-progress task dropped — a terminal state no claim can touch
    (done tasks are refused; dropping an in-progress task releases its claim
    marker so the resource can be re-queued) — and cairn task list --kind-prefix PREFIX filters whole task families, e.g.
    --kind-prefix wiki-page lists every hop of the wiki chains. cairn wiki status derives the new dropped per-page state from the live chain, and
    retry's failed-only selection never resurrects a dropped page.
  • Wiki rendering and onboarding: the dashboard's escape-first markdown
    renderer now renders inline code spans as <code> elements and GFM pipe
    tables as tables (still no inline HTML passthrough), so pages can use the
    two constructs technical writers lean on; and the generated agent docs
    (cairn install-agents) gain a ## Wiki section walking the
    generate → claim → complete → ask_compass consumption loop, making the
    consumer side of the wiki visible to new workspaces.

Changed

  • Test suite reduced by ~470 cases with the core regression net intact, in
    three reviewable parts: the closed retrieval-quality campaign machinery
    retired lockstep with its CLI flags (see Removed); bench/dataset/soak
    infrastructure tests moved to an explicit infra marker tier that per-PR
    CI legs skip (-m "not infra") but every main push runs, so the tier
    cannot rot; and subsumed pins pruned from the two largest audit-era
    files (test_install_uninstall_fidelity.py 74 → 32,
    test_embedding_backend_quality.py 70 → 35) plus duplicate suites
    (test_t2_snapshot.py, test_chunk_spike.py, the doctor-mirrored
    report tests, and the spike-era test_big_tech_improvements.py tests
    subsumed by their dedicated files) — every deletion justified by the
    surviving test that subsumes it. A new suite-hygiene tripwire pins the
    tier's shape (whole-file/class marks only, never per-test; no global
    pytest addopts, which would silently deselect the bench job's -k t2
    gate).
  • Wiki generation quality: the planner excludes test-majority modules — a
    strict majority of the module's indexed files under
    test/tests/spec/specs path segments — from page plans entirely
    instead of letting them outrank product code by degree (a mixed module
    keeps its page; only modules whose test files outnumber code files are
    dropped). The critic reports each unresolved path once per completion
    regardless of how many citation forms mention it (the same dead path
    cited in the body and the footer no longer double-reports), and the wiki
    output spec — the ## Sources footer requirement — now serves any task
    kind whose name starts with wiki-page, so revise hops and enrichment
    chains of any depth keep their output instructions.
  • Docs set synced with the wiki surface: docs/cli-reference.md gains
    wiki export/enrich rows, the dropped state and fresh/stale
    staleness on wiki status, and task drop/--kind-prefix;
    docs/knowledge-and-memory.md covers the commit-sha provenance, the
    staleness model, the test-majority exclusion, and the enrich append
    semantics; docs/mcp-tools.md extends the wiki workflow's
    consume-and-operate step.

Removed

  • The closed retrieval-quality campaign machinery, retired with its CLI
    surface: cairn eval --sweep/--out/--kfold/--folds, the seeded-split,
    k-fold, lever-sweep and paired-bootstrap engines in src/cairn/eval.py
    (~1,500 lines), the one-shot runner/analyze scripts under
    benchmarks/quality/{fr003-calibration,fr004-prf,fr005-mv,ladder-v2}/,
    and their test suites. The campaign closed documented-shortfall-no-ship
    (no retrieval lever shipped), so no behavior changes; cairn eval
    itself (yaml fixture + graded ground truth) is unchanged, and the
    committed artifacts under benchmarks/quality/ remain the record of
    the closed verdict (scripts recoverable from git history).

Fixed

  • A minimal install (pip/pipx from PyPI, no extras) crashed cairn dashboard on ImportError: jinja2 must be installed to use Jinja2Templates: starlette ships jinja2 only in its [full] extra, so
    the copy mcp pulls never brings it, and every dev/CI environment
    happened to install it via extras — masking the gap. jinja2 is now a
    declared runtime dependency (verified end-to-end in a base-deps-only
    venv), pinned by a test so it cannot be pruned as "unused" again.
  • cairn compass gaps reported every module as uncovered: detect_gaps
    compared repo-qualified module ids against repo-relative compass
    resource fields, so the match could never succeed. Coverage now
    compares same-repo (or untagged) compass resources against the module's
    repo-relative path, keeping the segment-boundary prefix rule.

[0.16.2] - 2026-08-31

Added

  • Live Mermaid rendering in the dashboard wiki detail view: mermaid fences
    now render as diagrams client-side (mermaid.js from CDN, strict security
    level), degrading to the visible code block when offline or JavaScript
    is unavailable. No new Python dependency — the renderer still emits
    escaped pre.mermaid blocks.

[0.16.1] - 2026-08-31

Added

  • Agent-decoupled wiki generation: cairn wiki generate --llm plans a
    deterministic page outline from the code graph (overview + top modules by
    cross-module incoming degree, --pages cap) and queues one wiki-page
    task per page for any agent to claim and complete; completions are
    critic-gated (the ## Sources footer is the wiki quality signal) and
    promoted as Wiki-Article concepts with verified sources frontmatter,
    while failing attempts run the existing bounded revise cycle. A
    .knowledge/_wiki/manifest.json manifest makes regeneration incremental
    (unchanged, promoted pages are skipped; --force re-queues everything)
    and powers cairn wiki status and cairn wiki retry;
    --refine-catalog queues a wiki-catalog refinement task whose
    validated outline drives the page tasks. The new wiki_generate MCP
    tool brings the server to 28 tools, and the dashboard gains a wiki tab
    listing pages with state badges and rendering promoted pages with their
    sources.
  • Dashboard graph UX and landing coverage: the graph side panel's
    callers/callees/impact/affected-test rows are deep links into the
    symbol-focused graph (the store rides the href), and the FR-013
    degradation banner links one click into the Embeddings status view
    carrying the matching rung detail. The landing launcher grid now
    covers every sidebar view, including the Embeddings and Settings
    sections added in 0.16.0.
  • Graph tab curation and side panel (dashboard): the module scope's default
    view now ranks candidates by degree (fan-in + fan-out) instead of an
    arbitrary first-50 rowid sample — which had been filling the canvas with
    test nodes — and excludes tests by default behind an explicit
    "Include tests" toggle, with tests_included/truncated reported in the
    scope metadata. A new /graph/inspect endpoint answers the panel's
    question for one symbol in a single call — identity (kind, file:line,
    docstring, same-name count), resolved callers and callees (capped, with
    honest truncation flags), and the depth-3 impact view with the
    affected-test classification reused from the impact layer. Selecting a
    node on the canvas fills the side panel; deselecting collapses it and the
    canvas keeps the full width.
  • Environment propagation and wiring verification for custom CAIRN_HOME
    installs (root cause of #70): every cairn-generated integration now
    carries the store location — stdio MCP registrations embed
    env.CAIRN_HOME across all nine client shapes (the Claude global-scope
    registration uses the verified claude mcp add -e form), generated hook
    commands and the git post-commit template embed the assignment, and the
    LaunchAgent plist propagates it to the daemon. Default-home installs
    generate byte-identical configs. cairn config --json is a new
    read-only resolution probe (cairn_home/workspace/db/knowledge);
    cairn install-agents spawn-verifies every written stdio registration
    and reports per-client PASS/FAIL naming both stores on mismatch; and
    cairn doctor gains a tenth environment check auditing store
    existence, client-registration consistency (mixed severity: FAIL on a
    provable wrong-store or unreachable SSE endpoint, WARN on stale
    missing-env registrations), platform/transport supportability, and
    binary coherence. A missing store directory now fails with the resolved
    path, the env resolution chain, and the remediation on both the server
    boot and CLI paths, and a changed-CAIRN_HOME reinstall refreshes
    stale env in the zcode/opencode/kilo config shapes.

Changed

  • CI type-check gate hardened: mypy was driven from 63 reported errors
    (advisory continue-on-error step) to a clean run across all 172
    source files and is now a hard gate that blocks CI on new type
    regressions, matching the ruff gate's philosophy. The fixes were
    annotations and narrowing for our own code; targeted type: ignore[...] markers with justifications only where stubs are
    genuinely absent (protobuf codegen, OTel SDK re-exports,
    pydantic-settings version skew, the <3.10 entry-points dict shape).
  • The 7 Medium-severity bandit advisory findings (6x B310 urlopen,
    1x B615 HuggingFace from_pretrained) are pinned with inline
    # nosec markers carrying their review rationale, so the advisory
    report stays empty while future genuinely-untrusted URL opens would
    still be flagged (no global skip list added).

Fixed

  • Eval harness latent crash in the lexical fallback: search_symbols
    returns sqlite3.Row rows, which have no .get — the fallback path
    (taken when semantic search raises, e.g. embeddings unavailable)
    crashed with AttributeError instead of degrading. Rows are now
    indexed directly (r["name"], guaranteed by the SELECT).
  • C# parser no longer emits an edge with a None target when a
    new T<...>() generic-name extraction finds no leading identifier;
    the edge is skipped instead.
  • Storing a memory concept whose title is missing no longer feeds
    None into the slugifier (tier-id paths in both the file-backed and
    protocol stores now coerce an empty title first).

[0.16.0] - 2026-08-28

Added

  • Embedding server backends — embeddings via any OpenAI-compatible
    /v1/embeddings server with zero torch footprint in the cairn process.
    CAIRN_EMBED_BACKEND gains server (explicit CAIRN_EMBED_BASE_URL),
    omlx (preset http://127.0.0.1:8000/v1), and ollama (preset
    http://127.0.0.1:11434/v1); oMLX, Ollama, LM Studio, llama.cpp, and
    vLLM all speak the same shape. The stdlib-urllib client chunks requests
    (CAIRN_EMBED_SERVER_BATCH, default 32), retries transient errors
    three times with jittered backoff, fails other 4xx loudly with the
    server's own message, and stamps rows server/{netloc}/{model} so the
    existing staleness/vec0 machinery works unmodified (measured bge-m3
    parity vs sentence-transformers: cosine 1.000000). Availability is a
    cached GET /v1/models probe that must list the configured model id;
    a server backend never silently degrades to hash vectors. Warmup fires
    one tiny probe at boot so the server-side lazy model load is off the
    first query. local, hash, and openai are byte-for-byte unchanged.
  • Verified migration between embedding producers —
    CAIRN_EMBED_MODEL_STAMP aliases a stored stamp and arms a parity
    gate before any write: up to 16 stored chunks are re-embedded through
    the new producer and the mean cosine must clear 0.98 (same weights
    measure ~1.000; different models land far below), else the pass
    hard-aborts quoting the measured value. Switching local bge-m3 to a
    server serving the same weights therefore costs zero re-embeds.
  • Fallback ladder + loud degradation — when the configured backend or
    model disappears, retrieval degrades in verified steps instead of
    crashing or lying: a parity-verified same-server candidate may serve
    the dense leg for the session (alias mechanics, zero re-embed;
    cairn embed --adopt-server-model makes the binding permanent), then
    a parity-verified local model, then the existing BM25/RRF hybrid with
    provenance="bm25". The hash backend is never a rung. Results whose
    dense leg actually fell carry degraded="embedding-backend" plus a
    remediation hint; every degradation fans out to one warn-once log
    line, one embed_server_degraded telemetry event (six-reason enum,
    host+model payload only), an MCP result footnote, a doctor entry, and
    a dashboard banner. This also fixes embed errors propagating uncaught
    out of semantic_search (previously a backend outage crashed the
    search).
  • Persistent configuration — ~/.cairn/config.json with precedence env

    file > default and mtime-based re-read, so running processes pick up
    edits without restart; file-persisted aliases arm the parity gate
    identically to env. The dashboard gains a Settings section (its first
    POST routes — loopback-only, confirm-gated base-URL changes, a
    write-only API key, live parity check) and an Embeddings status view
    (effective backend, resolved stamp, per-corpus counts and freshness,
    probe health, active fallback rung, per-knob source markers). cairn doctor grows embed-server probe/model-listing/parity/latency checks
    and config-source echo; the embed/semantic CLIs print server-specific
    remediation instead of the torch-install hint, and install_hint
    names the no-torch path. Docs: new server-backend sections in
    configuration.md (env table, oMLX safetensors walkthrough, privacy
    note) and retrieval.md (stamps, alias gate, ladder, doctor rule).

Fixed

  • Blank dashboard graph canvas: the module scope's name-based edge join
    multiplied same-named symbols across repos into parallel edges (10 of
    the 11 rendered edges were one __call__→append pair), and parallel
    edges send vis-network's force layout into a never-converging
    simulation — zero pixels painted, in every browser. Edges are now
    deduplicated on (source, target, kind) at the query layer and again
    client-side on initial load, and degree-ranked candidates exclude
    minified bundles and vendored/build paths (*.min.*, vendor/,
    dist/, node_modules/; metadata.vendored_excluded reports the
    count — one committed vis-network.min.js alone carried 1,060 junk
    symbols). The scanner gains a matching non-overridable
    minified_asset skip so future builds never index such bundles;
    existing stores are covered at query time without a reindex.
  • The topbar's live-refresh Pause control no longer renders (dead) on
    the eight views without a live region: the display: flex rule had
    been overriding the hidden attribute.
  • Health, Embeddings, and Projects views render stored timestamps
    humanized (2026-08-21 09:00:00 UTC, not raw ISO) and an
    unreadable vec0 row count as "row count unavailable" instead of the
    literal Python None.
  • A failed embedding backend no longer crashes semantic_search: the
    dense leg is guarded end to end (first pass, PRF second pass, ANN and
    brute-force legs) and hard failures flow into the ladder above.
  • Embedding-knob validation: invalid CAIRN_EMBED_SERVER_BATCH (zero,
    negative, non-numeric) and CAIRN_EMBED_TIMEOUT (non-finite,
    non-positive) fail loudly instead of crashing mid-pass or silently
    producing an empty index; malformed embedding-server responses are
    rejected with a body excerpt.
  • set_config_values fsyncs before the atomic replace; corrupt or
    non-object config.json degrades to defaults with a warning instead
    of silence; ladder probes and parity checks honor file-persisted API
    keys, timeouts, and local-model settings; the cardinality guard pins
    the degradation reason enum to a local literal so enum drift fails
    the guard.

[0.15.0] - 2026-08-27

Added

  • cairn knowledge ingest — staged multi-source document ingestion: repo
    doc-tree scans (allowlist walk, skip-list with logged reasons), fed
    markdown, and fed pdf/docx conversion behind the new cairn[ingest]
    extra (pymupdf4llm + mammoth + markdownify, lazy imports — the base
    install degrades to a reasoned skip). One parse/classify/identity/stage
    path produces an OKF outbox plus a dry-run manifest (staged output is
    redacted like the store); --ingest approval writes each row
    in-process via add_document, embeds, and verifies (count vs manifest,
    cairn validate, smoke search, idempotent re-runs). --include-drafts
    re-admits the whole draft-status family (tagged draft), not only the
    literal draft status. add_document gained an optional description
    parameter — surfaced as --description on cairn knowledge add, whose
    --resource flag is unchanged; with neither flag the default behavior
    is unchanged. Per-workspace overrides: cairn.json grows an ingest
    key layering classification rules and skip-list entries over built-in
    defaults.
  • install-agents now supports the omp CLI: detection and MCP wiring
    alongside the existing clients. omp gets the shared mcpServers shape
    in .omp/mcp.json (workspace) or ~/.omp/agent/mcp.json (global),
    plus the two native omp subagent files cairn-explorer.md and
    knowledge-steward.md under .omp/agents/ / ~/.omp/agent/agents/;
    uninstall strips the MCP entry and both agent files per scope
    (workspace, global, or all).

Changed

  • Docs set rewritten from a fresh code survey, agent-first: new
    docs/README.md index, architecture.md, indexing.md, retrieval.md,
    knowledge-and-memory.md, mcp-tools.md, cli-reference.md, and
    configuration.md, plus four standalone diagrams under docs/diagrams/
    (system architecture, indexing, retrieval, doc ingestion). Every
    identifier is verbatim from source; no hand-cited line numbers.
    README.md / AGENTS.md / CONTRIBUTING.md pointers rewired to the new
    set.

Removed

  • Legacy doc set (15 files) and the entire specs/ tree (102 files) —
    docs/review-checklist.md and docs/release-checklist.md are the only
    pre-existing docs kept.
  • Benchmarks-doc machinery retired with its target:
    docs/benchmarks.md, scripts/gen_benchmark_tables.py,
    tests/test_gen_benchmark_tables.py, and the CI step that guarded the
    generated tables (T018). Benchmarks themselves still run via
    cairn bench; only the generated-doc publication path is gone.

[0.14.4] - 2026-08-26

Added

  • install-agents now supports the kilo CLI (Kilo Code): MCP wired
    into kilo.json (workspace) or ~/.config/kilo/kilo.json (global)
    under the mcp key — the opencode-format schema kilo documents
    (type: remote + url under the SSE default, type: local +
    command-array for --stdio). Detection covers the kilo CLI on PATH,
    ~/.config/kilo/, and a workspace kilo.json/.kilo/; uninstall
    strips all scopes.

Fixed

  • The SSE-by-default transport now also covers the two subprocess MCP
    registrations: install-agents --client claude --scope global registers
    via claude mcp add --transport sse --scope user cairn <url> and a
    droid install with the droid CLI on PATH registers via
    droid mcp add cairn <url> --type sse. Both previously ignored the
    transport setting and always registered a stdio command spawn, so a
    global/default install silently ran one process per client while the
    CLI summary claimed SSE. --stdio still opts out; the shared daemon
    URL resolution is deduplicated in one default_sse_url helper.
  • agy (Antigravity) SSE installs now write the documented remote shape —
    mcpServers.cairn.serverUrl — instead of the shared type/url
    fields. Antigravity does not support legacy url/httpUrl fields
    (per its docs), so the previous entry was silently ignored and cairn
    never connected. The stdio shape (command/args) is unchanged.

[0.14.3] - 2026-08-26

Changed

  • install-agents now defaults to SSE transport for every supported
    client, instead of only when the SSE daemon happened to be reachable
    (falling back to stdio otherwise). Claude Desktop still always gets a
    stdio config — the app does not support SSE/HTTP MCP transports. If the
    daemon is not reachable at install time, the CLI prints a note to run
    cairn serve start; --stdio opts out globally.

Fixed

  • install-agents --scope global for ZCode now writes the MCP entry to
    ~/.zcode/cli/config.json — the file the ZCode CLI actually reads for
    user-scope MCP servers (skills/commands/agents still go to ~/.zcode/).
    It previously wrote ~/.zcode/config.json, which the CLI does not read
    for MCP servers, so global installs silently never connected — sessions
    reported "Required MCP server is not connected: cairn". Re-running the
    installer migrates an existing legacy entry (strips it from the old
    path), and uninstall/installed-detection now know both paths.

[0.14.2] - 2026-08-25

Focus: two robustness fixes — cairn embed --install-deps no longer
corrupts (and can now self-repair) when multiple Python interpreters share
a machine, and cairn upgrade / the model downloads show one progress
line instead of streaming pages of installer/downloader output.

Fixed

  • cairn embed --install-deps no longer breaks — and becomes unrepairable —
    when two Python interpreters share one machine. The semantic-dependency
    directory is now scoped per interpreter ABI (~/.cairn/lib/cp311/,
    ~/.cairn/lib/cp314/, …): the stack ships ABI-specific wheels, so a single
    flat ~/.cairn/lib silently corrupted when e.g. a 3.11 dev venv and a 3.14
    install both ran --install-deps into it. The mixed dir then failed every
    install verification (ImportError: cannot import name '_regex' …) and
    pip's --target skip-if-satisfied semantics meant re-running the install
    could never repair it — pip reported success while the broken cp311
    binaries stayed in place. Existing flat installs keep working without a
    reinstall (the legacy dir stays on sys.path, shadowed by the ABI dir),
    and an explicit CAIRN_LIB is honored verbatim. A failed post-install
    verification now wipes the lib dir and reinstalls once from scratch — the
    only sound repair for an interrupted or foreign-ABI install — and the
    no-pip uv fallback pins --python to the running interpreter so uv can no
    longer resolve wheels for a different ABI than the one running cairn.
  • cairn upgrade no longer dumps the installer's raw output into the
    terminal (50+ lines of pipx/uv/pip venv-creation and
    Collecting/Downloading/already-satisfied noise from a single upgrade).
    The reinstall now runs behind the same single live progress line as
    cairn embed --install-deps; the installer's output is captured and
    shown only when the upgrade fails, followed by the exact manual retry
    command, and a failed upgrade now exits non-zero instead of silently
    returning 0.
  • cairn embed --download-model and cairn download-reranker get the same
    treatment: the model fetch runs in a child interpreter behind one live
    progress line instead of constructing the model in-process, which let
    HuggingFace print one tqdm bar per repo file (plus transformers warnings)
    straight into the terminal for an ~836 MB / ~1.1 GB multi-file download.
    The child shares the parent's HuggingFace cache, so the weights are
    immediately available to every process; the downloader's captured output
    is shown only when the fetch fails.

[0.14.1] - 2026-08-25

Focus: one bugfix — cairn embed --install-deps (and
--download-model) no longer sit dead-silent and look hung while the
freshly installed semantic stack loads for the first time.

Fixed

  • cairn embed --install-deps no longer looks hung after the dependency
    download finishes. The post-install verification import now runs in a
    fresh subprocess under a live progress bar ("Verifying install …")
    instead of silently importing torch/sentence-transformers in-process —
    30s+ of dead-quiet terminal on a first install while macOS validates
    ~150 freshly installed native libraries, which users understandably read
    as a hang. The subprocess pays the same one-time cost, shows live
    progress, and warms the OS caches so the next cairn embed imports at
    full speed. cairn embed --download-model gets the same treatment: it
    now prints a "Loading the semantic backend …" line before its in-process
    availability import, which sat equally silent. The old in-process path
    also evicted numpy from sys.modules; numpy's C core cannot survive a
    second initialization in one process, so any later numpy import in that
    process would have failed.

[0.14.0] - 2026-08-25

Focus: a two-train release — Kotlin support moves to the
actively-maintained fwcd tree-sitter grammar (vendored in-tree as a compiled
extension, modern syntax parses clean, per-platform wheels), and the
dashboard gets its GitNexus-inspired visual overhaul with live typeahead
graph search.

Added

  • Modern Kotlin parses cleanly — files using recent Kotlin syntax (KEEP-0438
    destructuring in all four forms, when guard conditions, multi-dollar
    string interpolation, trailing commas in parameters/arguments/indexing/
    lambda and destructuring positions) now index without error nodes. A new
    scan test parses every fixture under tests/fixtures/kotlin/modern/ and
    fails on any ERROR/MISSING node, so grammar regressions surface
    mechanically instead of silently degrading graph extraction.
  • Dashboard visual overhaul — a sidebar app shell with GitNexus-inspired
    design tokens across every view: restyled tables, badges, cards, window
    controls, and chain pills; stat tiles, export chips, and segmented
    window controls; a landing launcher; a reworked graph view (kind colors,
    communities, label discipline, spiral layout, kind filters, counts
    legend, canvas controls). The graph's symbol search becomes a live
    typeahead: matching symbols appear in a dropdown while typing
    (prefix-match /graph/suggest endpoint, case-insensitive, shortest
    first, kind/file context), navigable by arrow keys, Enter, or click —
    the old type-blind confirm flow remains as the Enter fallback.

Fixed

  • Stale dashboard assets across upgrades — static responses now send
    Cache-Control: no-cache and asset URLs carry a mtime version, so a
    browser holding an old cached app.js never renders it against new
    HTML. The last selected workspace is remembered per browser
    (localStorage), so bare visits return to it instead of the launch
    store.

Changed

  • Kotlin grammar swapped to the vendored fwcd grammar — the dormant
    tree-sitter-kotlin PyPI dependency (last upstream change January 2025)
    is replaced by fwcd's actively-maintained grammar, vendored in-tree at a
    pinned commit (vendor/tree-sitter-kotlin/, NOTICE records the pin) and
    compiled into cairn's own wheels as the cairn._tree_sitter_kotlin
    abi3 extension. The dependency is removed outright — no fallback loader,
    a single Kotlin grammar path. Kotlin extraction (symbols, call edges,
    inheritance, imports, the operator fun invoke rewrite) is unchanged:
    the golden corpus stays byte-identical and the scip-java hybrid merge
    joins against the new parses unchanged. With the in-tree extension,
    cairn-intel wheels are now per-platform (cp310-abi3; macOS Apple
    Silicon, manylinux x86_64/aarch64, Windows) built by a pinned
    cibuildwheel matrix, with every wheel smoke-testing its own grammar at
    build time — pip install cairn-intel stays toolchain-free. Two
    platforms are deliberately not shipped: musllinux (sqlite-vec, a core
    dependency, publishes no musl wheels, so an Alpine install can never
    resolve) and Intel macOS (cryptography — pulled transitively via
    mcp's pyjwt[crypto] — has dropped Intel-macOS wheels; the first
    per-wheel install test is what surfaced both limits).
  • The dashboard subsystem is now documented: cairn dashboard entry in
    the CLI reference, a Local dashboard section in the architecture guide
    (read-only and loopback-only invariants, store selection, estimate
    provenance), and README coverage.

[0.13.0] - 2026-08-20

Focus: the dashboard-v2 train — cairn dashboard grows from a
read-only window into the graph into a complete operational console for
months of recorded agent traffic: scale, liveness, navigation, machine-wide
store switching, CLI-side usage recording, and an operational-polish pass.

Added

  • cairn dashboard — a local, read-only web dashboard (127.0.0.1:8765,
    zero new runtime deps): projects overview with index/embedding status,
    interactive project graph (vendored vis-network, scope/depth controls),
    tool-call history with filters, per-tool estimated token usage ranked by
    cost, session-grouped tool call chains split at inactivity gaps, plus
    health / memory / task-queue panels. A missing DB renders guidance
    ("run cairn build"), never an error page.
  • MCP tool-call recording — tool_metrics gains req_chars, resp_chars,
    and a redacted args_summary (additive migration; buffered off the hot
    path, flushed on clean shutdown), and the server stamps a per-process
    CAIRN_SESSION id so history and chains group per session.
  • Traffic views at months of scale — history/tokens/chains gain time
    windows (24h / 7d / 30d / all) and keyset pagination over a new
    (invoked_at, id) index, so a store with 10.5k+ recorded calls renders
    within its budget; chains are bounded with per-session expand.
  • Live traffic views — poll-based auto-refresh on history/chains/tokens
    (a re-arming loop gated on tab visibility) with pause/resume, a
    disconnected banner, and filter/scroll preservation across refreshes;
    proven by a 30-cycle soak asserting row-set equality.
  • Cross-view links — every entity drills down in one click: tokens→history
    and history→chains row links that carry the active window, a chains
    session filter, /graph linked from both navs, and node inspection via
    select (single click) with doubleClick reserved for expansion.
  • Graph navigation — symbol search-to-focus with a disambiguation
    candidates endpoint, doubleClick node expansion via /graph/neighbors,
    and a force/hierarchical layout toggle that preserves the camera and
    persists in the URL.
  • Workspaces launcher — /workspaces gives a machine-wide overview of
    every known store (registry ∪ directories, four-state enumeration,
    stat-first budgeted probes) and ?store= switches every view to another
    workspace without a server restart, with full filter/link carry.
  • CLI usage recording — cli:* command invocations are recorded beside
    MCP tool calls into tool_metrics with a source column (mcp/cli,
    additive migration, buffered like the MCP path): the history view
    displays and filters by source, the tokens view includes CLI usage under
    cli:* names, and terminal/tmux/CLI session ids never render as
    "unknown".
  • Dashboard operational polish — warm health: probe results (reranker/ANN
    checks) prewarm on a background thread at server start and serve from a
    stale-while-revalidate cache, so the first /health never pays the
    sentence-transformers import; the health panel gains a Retention card
    showing the policy in force and current store size. Token estimates
    become mode-aware: an exact tokenizer (via the optional [semantic]
    extra, no new deps) calibrates the chars-per-token divisor per window
    from the store's summaries, with the active mode always labeled and
    heuristic (chars/4) as the zero-dependency fallback. Truncation
    observability: tool_metrics gains truncated_from_chars/
    truncated_to_chars recorded per truncated call (durable past the
    events-table cap), and the tokens view surfaces per-tool truncation
    counts (unknown rendered distinctly from zero). Usage retention:
    tool_metrics now ages out under CAIRN_TOOL_METRICS_MAX_ROWS
    (default 50000) and optional CAIRN_TOOL_METRICS_MAX_AGE_SECONDS,
    applied by the recording pipeline's flush transaction — never by the
    read-only dashboard. Export: /history.csv|.json and /tokens.csv|.json
    export the current filtered view (window/tool/session/source filters,
    RFC-4180-correct CSV). Dark theme: [data-theme=dark] palette with a
    pre-paint, localStorage-persisted toggle defaulting to
    prefers-color-scheme.

Changed

  • cairn config --db — prints only the resolved graph DB path (machine-readable,
    for scripting). The README self-demo already piped this into sqlite3; the
    flag now exists to match.
  • Code-vs-docs drift fixes from a full docs audit. docs/mcp-tools.md: the
    freshness section now describes the live file watcher ([watch] extra, ~2s
    debounce, pending_sync staleness banners) instead of the pre-watcher
    boot-only model, and semantic_search documents its rerank parameter and
    the CAIRN_RERANK_MIN_MARGIN auto gate. docs/cli-reference.md: 15 (not 14)
    memory subcommands with cairn memory embed documented, update --knowledge, config --db, and the build --staging/--repo mutual
    exclusion. docs/configuration.md: CAIRN_WORKERS is clamped to [1, 256]
    (not uncapp