Repository navigation
[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. Av*tag now cuts the GitHub Release (changelog notes) whilerelease-rust.ymlattaches the binary tarballs, updates the Homebrew tap, and publishes to crates.io; Homebrew,cargo binstall, and the tarballs are the install channels. cairn uninstallreports the running binary and its channel-appropriate removal command instead of driving pip/pipx/uv;cairn reportno longer prints apythonline.- 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'sscripts/impact_guard.pyhelper: a cycle-carrying result above the threshold prints a name-collision warning and the dataflow-index fallback instead of the raw dump (--jsongains aguardobject), 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.shbuilds from a checkout withcargo install(no Python required),scripts/uninstall.shforwards tocairn uninstall, and the SCIP test fixture regenerates withcargo xtask scip-fixture.cairn upgradequeries PyPI throughcurlinstead of a Python interpreter.- Release bumps run through
cargo xtask bump(commitizen retired): it moves the Cargo workspace version,Cargo.lockandpyproject.tomltogether, commits and tags;--dry-rundrafts the changelog section. wheel_smoke.shprobes 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-stylegates long comment and doc blocks in the Rust crates (shrink-only baseline), andcargo xtask verify-protectionrequires 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 committedexpected.jsonschema instead.
Removed
CAIRN_RUST_BIN:CAIRN_BINis the single engine-binary override (daemon start,cargo xtask kg-dump, local CI scripts).- The stray-daemon sweep no longer recognizes python-launched
cairn serveprocesses. - The
cairn upgradecommand (and its--checkflag): it existed to pip/pipx/uv-upgrade the wheel installs; every remaining channel (Homebrew, cargo, tarball) upgrades through its own package manager. Theupgrade --checkcli-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) andscripts/rust_migration/wheel_smoke.sh. - Retired stubs and upgrade shims: the
download-rerankercommand; thebuild --lsp,init --import-docs,init --from-legacyandembed --install-deps/--download-model/--adopt-server-modelflags (now unknown-argument errors); thedoctoragent_hookscheck and every migration ofpython -m cairn.hooks.claude_hookshook commands; pruning of the retiredimpact_guard.pyskill file; unloading thedev.cairn.sseLaunchAgent; the automatic./cairnlegacy-store copy ininit; the dashboard's one-segment/wiki/<page>redirect; and the rebuild of pre-(symbol, repo)dataflowtables. - The optional
cairn-intel-ingestPDF converter package and theCAIRN_SIDECAR_PYTHONoverride:knowledge ingestalways converts PDFs natively (paragraph text). The repo now carries no Python code outside test corpora. cairn uninstallno 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, andcargo xtask verify-no-code-change. - Dead Python-era fixture tooling: the
tests/fixtures/__init__.pypackage markers andtests/fixtures/scip-indexing/generate_heavy.py(unrunnable in this tree — it required the retiredsrc/cairn/parsers/_scip_pb2.py). The committed parser-golden and SCIP fixtures remain; both regenerate from git history when needed. - The
openaivalue ofCAIRN_EMBED_BACKENDandCAIRN_EMBED_OPENAI_MODEL: an unrecognised backend name resolves to the local hash embedder. - The
memory_validity.backfillschema_metamarker: stores no longer write it.
[0.24.1] - 2026-10-26
Fixed
- The maturin sdist carries the declared
LICENSEfile (PyPI rejected the 0.24.0 sdist withLicense-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.shresolves the installed engine and the venv interpreter by their real.exespellings before the extensionless forms (msysls/statreport 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 samecommunitiesandsymbol_communitiestables 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
stdfile 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 thewin_amd64wheel leg and anx86_64-pc-windows-msvctarball. - Local CI mirroring GitHub's workflows in Apple containers (
scripts/ci-local.shviamake 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.shadditionally lints the Windows cfg arms from macOS. - Native document conversion in the engine (rust-only spec, FR-002/FR-003):
knowledge ingest --fileconverts.docxto markdown (headings, paragraphs, emphasis, lists, tables, links) and extracts.pdftext with no Python present; the optional high-fidelity PDF converter moved to its owncairn-intel-ingestpackage (pip install cairn-intel-ingest) and is used automatically when importable, keeping the AGPL dependency (pymupdf4llm) out of the engine; thecairn-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), andscripts/rust_migration/baseline.py --comparegates 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 --lspnow exits 1 naming the pyright-pass retirement instead of silently doing nothing (rust-only spec, FR-012, decision D-R07);--lsp=falsestays a default-equal no-op.- The PyPI wheel is the engine binary (rust-only spec, FR-004/FR-005/FR-006): maturin builds
cairn-intelfrom the cargo workspace with the nativecairnexecutable as its only payload (py3-none-<platform>, no Python launcher or module), sopip install,pipx installanduv tool install cairn-intelrun 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), andscripts/rust_migration/wheel_smoke.shprobes 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'sPath.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.gitattributespins 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 communitiesis 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 underpackaging/cairn-intel-ingest/. CI drops the mypy leg and the bandit-over-src scan (bandit now advises overscripts/andpackaging/; 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-cairnlookup) — 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.shdrops the retired--semanticpath.cairn prs,benchandevalstay retired — the Python engine they measured is gone and the binary carries no arm for them (decision D-R10 inspecs/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_BINenv > bundled package path > PATH; the python fallback andCAIRN_DAEMON_IMPLprecedence are unchanged), the release pipeline injects the per-platform binary into the wheel builds while the sdist stays binary-free, andscripts/rust_migration/wheel_smoke.shproves 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
dataflowtable 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-agentsaborts a client whose existing config cannot be backed up (matching python), and saving dashboard settings refuses to replace an unparseableconfig.jsonand keeps its non-scalar keys (rust and python). - Rust
cairn build --stagingswaps the staged store into place under the build lock instead of leaving<db>.tmpbehind; 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_flowreports 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_goldenrust test: TempStore scratch dirs named from wall-clock nanos collided under macOS parallel test threads; now named from a process-global counter. install-agentsclient-schema conformance, verified against each agent's official docs: agy (Antigravity) workspace installs now write the documented.agents/mcp_config.jsoninstead of ignoring workspace scope, its global path is~/.gemini/config/on every Unix (no XDG deviation), and its entry drops the undocumentedtypefield; Cursor entries drop thetypefield (its remote schema documentsurl+headersonly); the install probe andcairn doctoraudit 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 underrust/(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 aCAIRN_DAEMON_IMPL=rustopt-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.kgdump/diff,cli_golden.py, andg2_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/andbrainstorms/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
mcpServerspop 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_memorycommits the pooled connection; knowledge write tools refuse underCAIRN_READ_ONLY; the launchd plist carries the API key;serve restartkeeps the daemon's own bind;serve stopverifies 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 --stagingrefuses to swap an empty build over the live store;cairn configrenders a clean line for a server embed backend withoutCAIRN_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 thecontext/ask_compassmemory sections) match concepts again through a sharedrelativize_idhelper; 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_calleesorder before LIMIT; the watcher sweeps deleted untracked files; scip join-anomaly accounting counts dropped targets. - MCP server and daemon:
--hostreaches the daemon plist/exec argv;serve statusprobes the daemon's configured bind; cross-store daemon reuse is refused;serve stopsurvives launchctl failures;generate_flowandrecall_memoryhonorCAIRN_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 updateexits non-zero on reindex errors and resolvesA...Bdiff ranges; three-dot staging;--dry-runis honored on every compass write path; wiki search scopes to wiki pages and export confines paths to the target directory; the dashboard refuses saves underCAIRN_READ_ONLYand 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_RERANKandCAIRN_MAX_RESULT_CHARSinside 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-onlyopt-in). The stdio and SSE transports are removed;cairn serve --transport <any>exits non-zero naming the daemon URL andcairn connect. Agent registrations are URL + key form for the 8 supported clients (Claude Desktop reported unsupported — no remote capability);cairn initgenerates a user-onlyCAIRN_MCP_API_KEYembedded in generated configs;cairn doctor --fixrepoints legacy stdio/SSE registrations; registration verification probes/healthzinstead of spawning processes. cairn connectstarts or reuses the single daemon (flock-guarded, stray-sweeper enforced) and prints its URL;serve start|stop|status|restartmanage the HTTP daemon (launchd on macOS, daemonize + pidfile elsewhere), and the sweeper now recognizes interpreter-prefixedserveprocesses.- MCP rev 2026-07-28 features (MRTR,
subscriptions/listen, header validation) arrive via SDK bump only — no hand-rolled transport code (mcppin 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 byscripts/gen_conformance_table.pybetween 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 buildupgrades 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 viacairn.json(lsp.budget_seconds,lsp.edge_budget);--lspforces an unbounded pass,--no-lspskips. Upgrades persist, so a budget-stopped pass resumes from the remainder on the next build, and the report carriesprobed/budget_hitfor 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_styleno longer emits the runpy double-import warning (deferred collector imports inreport.py), andscripts/regen_scip_pb2.shnow emits the trimmed_scip_pb2.pyheader so regeneration cannot reintroduce debt. - Quality ratchet:
make audit-statusprints remaining-vs-total audit findings by priority fromdocs/audits/2026-10-02.mdplus a strict.status.jsonfix sidecar, andmake comment-styleenforces the AGENTS.md comment contract oversrc/through a machine-generated shrink-only baseline (comment-style-shrinkre-derives it after fixes; growth fails). Both gates run as thecheck-comment-lengthspre-commit hook and thequality-ratchetCI job, exposeARGS=--json, and surface as a count-onlyquality_gatessection incairn report. The legacy rootarchitecture.htmland audit report moved todocs/diagrams/anddocs/audits/. - Rationale indexing:
cairn buildcaptures comments whose text begins
NOTE:,WHY:, orHACK:in every language with a mapped comment node
type and persists them in an additiverationaletable, 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, andTODO:/FIXME:markers are out of scope). The new
cairn rationalecommand (--symbol/--filetargets,--db,--json)
prints records ordered by line with kind tags (note/why/hack); MCP
exploreoutput adds a gatedRationalesection when any matched symbol
has records and omits it otherwise, with the MCP tool count unchanged;
incremental reindex andcairn updatedelete 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..Bscopes 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/uninstallmanage 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 — pluscairn 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 workspacehooks installprints guidance and exits 1 while
init --with-hookswarns and init still succeeds — no partial hooks
either way. cairn communitiescommand: deterministic Louvain community detection over
structural edges, persisted to thecommunities/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
/communitiesview: renders the partition with community sizes,
hub symbols, and member drill-down (nav under Explore, after Graph). - HTTP serving:
cairn serve --transport httpexposes 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/healthzprobe, an optional--statelessmode for
load-balanced deployments, and a read-only default; ships with a slim
non-root Dockerfile (CAIRN_HOME=/datavolume, 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|contesteddeclares a
prior at record time, and the newcairn memory reflectrecomputes
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 incairn memory search/listoutput and MCP
recall_memory/exploreoutput. 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 theghCLI over the
workspace's remote;--impact PR|branchresolves the PR's diff (fetched
viagh 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;--conflictsranks open-PR pairs by shared-community
overlap of their touched symbols (largest overlap first, PR-number
tie-break), consuming thecommunities/symbol_communitiestables
(never building them) and printing arun cairn communitieshint
instead of failing when the tables are absent or empty;--jsonemits
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 --repono longer
crashes with an IntegrityError on files containingNOTE:/WHY:/HACK:
comments and no longer strands a permanentbuildingmarker (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_flowvisits 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
genericimplementsedges 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, thedroppedtask filter works, template and
knowledge-graph JS duplication collapsed onto shared helpers. - Wiki queue: manifest write failures surface to the CLI,
--dry-runis
honored in the--llmbranch, enrich tasks carry the diagrams flag, and
slug-collision page ids dedupe. - Destructive/privacy scope:
uninstall --graph-only -yresolves 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 tocairn 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_REFRESHandCAIRN_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'sApplication Supportlocation no longer exits 127), and
reinstalling agent hooks replaces stale Cairn commands instead of treating
any matching entrypoint as current. - Dashboard
/healthno longer returns 500 when the embed backend is a
server family with no resolvableCAIRN_EMBED_BASE_URL: the failing probe
degrades toNoneand 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 NULLinvoked_at(legacy-shape
tool_metricstables): thenextcursor is omitted instead of being
unparseable. cairn doctormarkup-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-agentswrites 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 --repono longer crashes with an integrity error on any repo
embedded with the default multivector pass:_clear_repodeletes the
repo'sembeddings_mvrows before deleting its symbols.cairn doctorno longer spawn-executescommand/args/envread
verbatim from workspace-owned registration config files: only an entry
exactly in the shapecairn install-agentswrites 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_refsread 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: everyfileslookup on the incremental path
(reindex_paths,_find_tracked_file_row, derived-prestate capture) is
scoped byrepo_id, matching theUNIQUE(repo_id, path)schema. cairn updateand the live watcher in git repos now index newly created
(untracked) source files: the changed-file signal merges
git ls-files --otherswithgit diff --name-only HEAD, so the watcher
no longer erases theirpending_syncstaleness 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
(mismatchedSec-Fetch-Site/Origin, 403) before touching config —
loopback binding alone does not stop a web page from posting to the port. cairn memory consolidateunlinks 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: L4queries to the L4
retriever and emits the L4 report bucket (previously scored by the L5
knowledge retriever and silently dropped). get_calleesapplies the same per-filepending_syncstaleness check as
get_callers, andget_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;initandbuildshare 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).scipindex 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) withscip_gen_*degradation
records;cairn import-scipCLI and thescipconfig key;[scip]
extra restoring the vendored protobuf stub;statsgainsedge_sources
andexact_share_by_language, build summary rendersscip: n edges (d disagreements);cairn updatere-applies the configured overlay after
reindexing (import-only, never a generation);docs/scip.mdtoolchain
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 incrementalmaintain_transitive_closurecall at
the same point, whose reads are scoped to the affected subgraph. edgestable gains an additivesourceprovenance column (NULL reads as
tree-sitter); local commit gate's staged-files ruff honors
extend-excludevia--force-exclude.
Fixed
- Agent hook stdout stays protocol-clean:
post_editandsession_end
emit cairn/prose diagnostics to stderr instead of stdout (the calling
agent parses Stop/PostToolUse hook stdout as JSON), andsession_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
localsymbols 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_overlaylogs a failed config read like every other
degradation instead of returning silently.cairn import-scipexits 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.shsplices 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
(additivememory_validityprojection + concept extensions, one-time
backfill);recall_memory/cairn memory searchaccept as-of dates;
cairn validate-paths --markexpires 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,cpublishes a learning to other agents' recall;cairn memory checkwarns 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.jsontaintoverrides), inter-procedural
propagation over precise call edges (--fuzzyopt-in, depth-capped),
cairn taint --from --topath tracing, and taint-path warnings in
exploreandcairn blastoutput. - 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-eventrecords resolved review comments as draft-tier
symbol-keyed memories; advisory.github/workflows/review.ymlposts
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. Benchpack-fit-ratearm: fit 1.00 at
99.7% token reduction vs the grep baseline.- Cross-repo federation:
federated_searchMCP tool (inventory is now 25
tools) andcairn federated-searchmerge 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-reposcomposes per-repo answers; opt-in--shared-embedserves
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;datasetsrides the new
benchextra); byte-identical reruns, hermetic-m coreCI 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-triggeredextension-release.ymlbuilds 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-refreshand read-only stores answer from the
stored graph with a probe-derived staleness banner (no[watch]extra
required). cairn blastcomputes the reverse-edge blast radius of a git diff
(working tree vsHEAD, 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 + MCPrepo_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
## Notessections
byte-identically across regeneration; the critic does not evaluate Notes
content. cairn viz --export FILE.htmlwrites 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 toexact; missing/failing server is a no-op with a
notice; existingexactedges are never downgraded.- Rust (
.rs) indexing via a generic tree-sitter tier (pinned
tree-sitter-rust); call edges stayunresolved(neverexact). discover_reposoptionally 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 Ncaps the public symbols indexed per
run;CAIRN_DATAFLOW_MAX_SYMBOLSsets 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 pinsonnxruntime<1.24on Python 3.10 so
pymupdf-layoutresolves to an installable wheel set. - Dataflow builds upsert rows in batches and compute the repo-keyed
cross_repo_depspayload 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-jobtimeout-minutes.
[0.20.2] - 2026-09-13
Added
cairn statsreports edge resolution shares (exact/ambiguous/
unresolved) over the calls/references candidate pool.cairn updateembeds changed files' symbols when a semantic backend is
reachable, and reports deferred embeds when none is available.cairn.jsoncommitted at the repo root excludes vendored dashboard static
chunks and benchmark datasource corpora from indexing.
Changed
cairn embedbuilds the multivector kinds (name,docstring) by
default;--no-multivectorrestores single-vector builds.reindex_pathssettles 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.jsonscipkey, 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 stalescipkey incairn.jsonis 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:symbolandimpactscopes 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, andvendored_excludedso 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
reposcope with no repo id buckets every repo in the store,
capped at 30 buckets with an honesttruncatedflag in metadata.
- The MCP
visualize_graphtool rides the same viz layer: module
diagrams include their real internal edges, andformat="json"
output for the repo scope carries thetruncatedfield. - 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 workspacelabel applies only to custom--dbpaths 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 nowcalls,extends,implements,
embeds,with,references,decorates,imports,contains.- Python base classes emit
extends(wasimplements); decorators emit
decorates; signature type annotations emitreferences. Swift splits
a class's first inheritance target toextends(the superclass) from
protocol conformances (implements). Go struct/interface embedding
emitsembeds. Kotlin type annotations emitreferences. - Every build synthesizes one
modulesymbol per file (dotted
repo-relative qualified name), derivescontainsedges for nesting
(module → top-level, parent → nested), gives module-level code an edge
owner, and materializesimportsmodule-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
importsedges. - 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 ascalls). - 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),embedsjoins
extends/implementsin the receiver-dispatch ancestor index, and
the new kinds stay outsideSTRUCTURAL_EDGE_KINDSso 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).
- Python base classes emit
Fixed
- The doc-link completion gate now confines proposed edges to the
completing task's island members: a result naming an existing doc
outsidefacts.membersis rejected with no writes (the task stays
in-progress and re-completable), instead of writingkind: inferred
edges between arbitrary knowledge docs.
[0.19.0] - 2026-09-09
Added
- The dashboard gained a
/knowledgecatalog 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 derivedknowledge_edgesindex), 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 namingcairn 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 relatedprints; 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/graphrelationship 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
theirrelation+kindso 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-changedbroadcast 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 dynamicimport()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 viamermaid.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.icopath serves the same
file, so the browser's automatic icon probe is a 200 instead of a 404
on every page load. - The
/graphsymbol inspect panel loads as an htmx fragment: the
node-click fetch rideshtmx.ajax(theHX-Requestheader 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-panelwith
no page navigation (sidebar/topbar persist by node identity). A stale
response from an earlier selection is cancelled athtmx:beforeSwap;
requests without the header keep the JSON payload. The
/knowledge/graphinspect fetch rides the samehtmx.ajaxseam.
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-changedevent on a theme flip (onedata-themeobserver
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/wikiare htmx
triggers (input changed delay:300ms; the tasks select onchange)
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-regionlive refresh on/history,/tokens, and/chains
is now an htmx poll (hx-trigger="every 5s"): each cycle re-fetches
the view'sHX-Requestbranch 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 viahtmx:beforeRequestwhile 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 theHX-Requestheader. 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/resultsroute (the same view/workspace
seed composition plus the/graph/suggestsymbol 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-visiblering (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 anaria-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: reducecollapses
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 thealpine-morphhtmx extension (alpine-morph.js). The shell
loads all four from/static/with the?v=cache-buster, runs htmx with
allowEvaldisabled via thehtmx-configmeta, and ships the
[x-cloak] { display: none }rule so Alpine-managed DOM never flashes
pre-init. Route handlers gain theis_hx_requestseam (theHX-Request
header) for serving full pages vs fragments from one route.
Added
cairn knowledge list --jsonemits 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 --jsonor sqlite. Human output and the--type/--status/
--tagfilters are unchanged.- Dangling relationship pointers are surfaced instead of silently
skipped: a declaredrelates_topointer 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), inknowledge ingest --ingestoutput (from its post-write
index rebuild), and incairn knowledge rebuildoutput. 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 theknowledge ingestflag set and the 22-tool MCP
surface are unchanged. - Island detection behind the LLM task queue: connected components over the
knowledge_edgesdoc graph surface pair-units detached from the corpus —
two-doc components and id-ordered singleton pairs — and queue one
doc-linktask per pair viaknowledge ingest --ingestandcairn 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 adoc-linktask throughcairn task claim/task complete --result-fileis gated by a deterministic critic that verifies every
proposed edge references existing knowledge concept_ids. A valid result
is written back askind: inferredrelates_toentries on both docs'
frontmatter (supersedesmirrors tosuperseded-by) and, through the
index rebuild, askind: inferredknowledge_edgesrows. 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. Newcairn knowledge islandsverb lists islands and their
task state (read-only). - Stored-edge search expansion:
search_knowledge's related-doc expansion
reads theknowledge_edgesrelationship index instead of recomputing
tag/module overlap at query time. Neighbors connected byextractedor
derivededges 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);inferrededges 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 OKFverifiedfamily
and, through the post-ingest rebuild, asknowledge_doc_refsrows. Bogus
refs appear nowhere; docs without resolvable refs gain noverified
family.add_documentgains an optionalverified_refsparameter and the
ingest report/CLI output carries averified_refscount. - Derived knowledge-relationship index:
knowledge_edges(doc_id, related_id, relation, kind, provenance, created_at)andknowledge_doc_refs(doc_id, ref, ref_kind, verified)tables (additive, idempotent schema) rebuilt from
the OKF bundle bycairn knowledge rebuildand automatically after
knowledge ingest --ingest. Declaredrelates_topointers 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 oraffects_modulesmaterialize symmetrickind: derived
edges;sources/verifiedentries with arefbecomeknowledge_doc_refs
rows. Rebuilds are idempotent — unchanged bundles reproduce identical table
contents includingcreated_atstamps.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
withNNNN-numbered filenames gainsupersedes/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 onParsedDoc.extensionsinstead of
discarding them;relates_to/supersedes/superseded-byfrontmatter
normalizes into arelates_toextension ({concept_id, relation, kind},
kind defaulting toextracted) that rides the staged OKF file, the
dry-run manifest row, and the promoted.knowledgefrontmatter;
add_documentgains an optionalrelationshipsparameter; dry-run
cairn knowledge ingestoutput lists each staged doc's relationships
before--ingestwrites anything. - Tribal-memory loop closure:
exploresurfaces a tribal-memory section
(≤3 entries, title + "How to apply" line) and recordsmemory_refsrows
for rendered memories;session_starthook emits the top tribal-memory
digest once per session;post_tool_failurecaptures mistake memories on
the second occurrence of a normalized failure signature
(memory_failure_signaturestable +cairn memory record --recurrence-key) and joins the installed hook set;capture_memory
force-routes session-bookkeeping-shaped content to the 7-dayrawtier;
session_endreads the Claude Codetranscript_pathJSONL and queues
memory-extract(the capture path was previously unreachable); memory
scoring drops the constantcritic_score/authorityterms and
renormalizes the five live signals; new L4 eval level (recall@k/MRR for
search_memory) and acairn doctormemory_stalenesscheck WARNing on
write-only tribal memory; six memory lifecycle tools leave the MCP
surface (28 → 22 tools) remaining available viacairn memoryCLI verbs
(cairn memory demotegains--db).
Changed
- Dashboard views restyled onto the token system end to end: every view's
repeated markup renders through shared Jinja macro libraries
(_table.htmldata tables + empty states,_filters.htmlfilter forms
carrying the hidden store field,_cards.htmlstat tiles + titled
cards), so table, filter-row, and summary-tile markup is identical
across views, and the legacygraph-controlsform 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-2over--bg-1
panels. Controls reach final density —--control-h30px 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 (#ffffffpage,#f7f8f8/#f2f3f4/#ececeesurfaces,
#17181atext) and[data-theme="dark"]restating the dark defaults;
both theme blocks redefine every token and setcolor-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 noprefers-color-schememedia 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;
schemacairn-wiki-manifest-3with v2/v1 manifests migrating on load),
while promotedWiki-Articleconcepts remain the sole record of what
exists (body, sources, provenance sha/task id). All lifecycle state is
derived at read time by the newcairn.wiki.lifecyclemodule (one
promoted-check implementation instead of five), the dashboard and CLI
expose derived state only, staleness never verdicts on pages without
content, completions writedoneonly 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-Reportunderreports/architecture/). The wiki is the
agent-facing knowledge surface for the whole workspace — code or
documents — searchable viasearch_knowledge/ask_compassand
explorable in the dashboard. First generate after upgrade re-queues all
pages once (the input-hash recipe now coverssourceand document
seeds); in-flight legacy tasks complete normally.
Fixed
- The /history tool filter narrows rows again: the filter matched the
storedtool_namecolumn 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/.jsonexports 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_LIBalong
with the already-patchedCONFIG_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~/.cairnstores 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_tobasename pointers resolve only a UNIQUE resource match: a
pointer liketarget.mdwhose 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 pointernaming 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, withadd_documentwriting over existing docs in
place), so an incrementalknowledge ingest --ingestinto 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-writeverify_manifest
use keeps comparing the batch alone against the store as it stands. - Re-ingesting a document preserves its promoted
kind: inferred
relates_toentries: 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
rebuiltknowledge_edgesrows — keep approved LLM links across
re-scans. relates_topointer resolution tries resource-prefixed forms: fed
documents promoteworkspace/<relpath>resources, so a bare
repo-relative pointer (e.g.docs/adr-0001-postgres.mdor 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 deriveddataflow-lookupfrom the function name, so the
documentedcairn dataflow lookupform was not invokable); the shipped
impact_guard.pyskill helper and its eval rule call the corrected name. - Agent install (droid):
droid mcp addmatches the documented stdio
shape (the full server command as one argument,--type stdio) and
pins a non-default home via the CLI's--envflag (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.jsonfile registration;droid mcp removereports
non-zero exits instead of staying silent. The user-scope
registration file (~/.factory/mcp.json) is now read by
check_installedand the doctor's registration audit, matching the
other clients' global configs. - Agent install (
install-agents --scope global, Claude Code): the
claude mcp addregistration places-eentries before a--
separator so server commands carrying flags (the
python -m cairn.cli.mainfallback) 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_installedand 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)
- directory paths resolve (
- 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
--repohint instead of mixing repos repo/modulepaths 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
/databaseview:- sqlite-vec internals (
vec_*,vecmv_*) are excluded - unintrospectable tables are skipped and reported (banner), never
fatal
- sqlite-vec internals (
- Knowledge relationships hardening:
- the
knowledge_doc_refsrebuild dedupes on its(doc_id, ref, ref_kind)primary key keepingverified=max, so a ref carried by
both thesourcesandverifiedfamilies with differing flags
indexes once instead of crashing the rebuild (which runs after
everyknowledge ingest --ingest) - a
doc-linkcompletion 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
Iterableof entries (generators
included) and the manifest serializes non-JSON extras (e.g. YAML
date values) viadefault=strinstead of crashing the dry run supersede_chaindocuments its linear-chain assumption and asserts
the queried doc's membership in the result
- the
- Built wheels ship the dashboard's whole asset tree: the package-data
glob forstatic/is recursive (static/**/*), so nested asset
subtrees reach wheel installs — a flatstatic/*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 overtemplates/andstatic/, so a data file the
globs miss fails the suite instead of surfacing as a runtime 404 on
installs. /historypolls hold the active page: the polled region bakes its
before/aftercursor 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-Requeston 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.icoanswers a missing vendored icon with 404 instead of an
unhandled 500.cairn buildexits 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 withstore parent directory does not exist, and the failure surfaced only as adataflow skippednote 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./knowledgeout-of-vocabulary filter values fall back to no filter,
matching the/tasks/memory/wikiconvention (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 readall. 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
/knowledgearchived status badge renders in the warn palette
like its sibling statuses instead of the unstyled base badge, and the
doc-detail provenance payload drops anoriginkey no template read. - The graph canvases' inspect fetches abort their superseded
predecessors (htmx:beforeSendcarries the live xhr) instead of
letting stale responses complete in the background, and the dead
htmx:timeoutlisteners are gone — htmx fires that event only with a
configured timeout, which no request in the dashboard sets. - A wiki manifest whose
pagessection is not a mapping (e.g. a list
from an external edit) fails with a clearValueErrornaming the
expected{repo}/{page_id}-keyed mapping instead of a bare
AttributeErrorfrom 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/wikicatalog
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
*_idreferences implied by column naming (a column already covered
by a declared FK is never re-inferred), internalsqlite_%/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-controlsrow (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
codechips (memory IDs, history sessions, task IDs/resources) cap to
one ellipsized line with the full value on thetitletooltip 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; aCtrl/Cmd+Kcommand palette searching
views, workspaces, and live symbol suggestions through the same
/graph/suggestendpoint 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 oneNAV_SECTIONSsource 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'soverviewpage 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 andsettings_saveresolves 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 bothcairn wiki status
and the dashboard wiki views compare it with the repo's current HEAD —
freshwhen they match,stalewhen both are present and differ,
unknownwhen 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 asDIR/{repo}/{page_id}.mdwith its OKF frontmatter
preserved (a non-empty target directory is refused without--force) and
reports the exported count, andcairn wiki enrich [<page-id>|--all]
queueswiki-page-enrichtasks 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
## Sourcesentries 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) — andcairn task list --kind-prefix PREFIXfilters whole task families, e.g.
--kind-prefix wiki-pagelists every hop of the wiki chains.cairn wiki statusderives the newdroppedper-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## Wikisection walking the
generate → claim → complete →ask_compassconsumption 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 explicitinframarker 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.py74 → 32,
test_embedding_backend_quality.py70 → 35) plus duplicate suites
(test_t2_snapshot.py,test_chunk_spike.py, the doctor-mirrored
report tests, and the spike-eratest_big_tech_improvements.pytests
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/specspath 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## Sourcesfooter requirement — now serves any task
kind whose name starts withwiki-page, so revise hops and enrichment
chains of any depth keep their output instructions. - Docs set synced with the wiki surface:
docs/cli-reference.mdgains
wiki export/enrichrows, thedroppedstate and fresh/stale
staleness onwiki status, andtask drop/--kind-prefix;
docs/knowledge-and-memory.mdcovers the commit-sha provenance, the
staleness model, the test-majority exclusion, and the enrich append
semantics;docs/mcp-tools.mdextends 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 insrc/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 underbenchmarks/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 dashboardonImportError: 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 gapsreported every module as uncovered:detect_gaps
compared repo-qualified module ids against repo-relative compass
resourcefields, 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
escapedpre.mermaidblocks.
[0.16.1] - 2026-08-31
Added
- Agent-decoupled wiki generation:
cairn wiki generate --llmplans a
deterministic page outline from the code graph (overview + top modules by
cross-module incoming degree,--pagescap) and queues onewiki-page
task per page for any agent to claim and complete; completions are
critic-gated (the## Sourcesfooter is the wiki quality signal) and
promoted asWiki-Articleconcepts with verifiedsourcesfrontmatter,
while failing attempts run the existing bounded revise cycle. A
.knowledge/_wiki/manifest.jsonmanifest makes regeneration incremental
(unchanged, promoted pages are skipped;--forcere-queues everything)
and powerscairn wiki statusandcairn wiki retry;
--refine-catalogqueues awiki-catalogrefinement task whose
validated outline drives the page tasks. The newwiki_generateMCP
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, withtests_included/truncatedreported in the
scope metadata. A new/graph/inspectendpoint 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_HOMEacross all nine client shapes (the Claude global-scope
registration uses the verifiedclaude mcp add -eform), 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 --jsonis a new
read-only resolution probe (cairn_home/workspace/db/knowledge);
cairn install-agentsspawn-verifies every written stdio registration
and reports per-client PASS/FAIL naming both stores on mismatch; and
cairn doctorgains a tenthenvironmentcheck 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_HOMEreinstall refreshes
stale env in the zcode/opencode/kilo config shapes.
Changed
- CI type-check gate hardened: mypy was driven from 63 reported errors
(advisorycontinue-on-errorstep) 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; targetedtype: 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 HuggingFacefrom_pretrained) are pinned with inline
# nosecmarkers 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
returnssqlite3.Rowrows, which have no.get— the fallback path
(taken when semantic search raises, e.g. embeddings unavailable)
crashed withAttributeErrorinstead of degrading. Rows are now
indexed directly (r["name"], guaranteed by the SELECT). - C# parser no longer emits an edge with a
Nonetarget 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
Noneinto 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/embeddingsserver with zero torch footprint in the cairn process.
CAIRN_EMBED_BACKENDgainsserver(explicitCAIRN_EMBED_BASE_URL),
omlx(presethttp://127.0.0.1:8000/v1), andollama(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 rowsserver/{netloc}/{model}so the
existing staleness/vec0 machinery works unmodified (measured bge-m3
parity vs sentence-transformers: cosine 1.000000). Availability is a
cachedGET /v1/modelsprobe 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, andopenaiare byte-for-byte unchanged. - Verified migration between embedding producers —
CAIRN_EMBED_MODEL_STAMPaliases 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-modelmakes 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 carrydegraded="embedding-backend"plus a
remediation hint; every degradation fans out to one warn-once log
line, oneembed_server_degradedtelemetry 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 ofsemantic_search(previously a backend outage crashed the
search). - Persistent configuration —
~/.cairn/config.jsonwith precedence envfile > 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 doctorgrows 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, andinstall_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__→appendpair), 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_excludedreports the
count — one committedvis-network.min.jsalone carried 1,060 junk
symbols). The scanner gains a matching non-overridable
minified_assetskip 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: thedisplay: flexrule had
been overriding thehiddenattribute. - 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 PythonNone. - 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) andCAIRN_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_valuesfsyncs before the atomic replace; corrupt or
non-objectconfig.jsondegrades 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 newcairn[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);--ingestapproval writes each row
in-process viaadd_document, embeds, and verifies (count vs manifest,
cairn validate, smoke search, idempotent re-runs).--include-drafts
re-admits the whole draft-status family (taggeddraft), not only the
literaldraftstatus.add_documentgained an optionaldescription
parameter — surfaced as--descriptiononcairn knowledge add, whose
--resourceflag is unchanged; with neither flag the default behavior
is unchanged. Per-workspace overrides:cairn.jsongrows aningest
key layering classification rules and skip-list entries over built-in
defaults.install-agentsnow supports the omp CLI: detection and MCP wiring
alongside the existing clients. omp gets the sharedmcpServersshape
in.omp/mcp.json(workspace) or~/.omp/agent/mcp.json(global),
plus the two native omp subagent filescairn-explorer.mdand
knowledge-steward.mdunder.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.mdindex,architecture.md,indexing.md,retrieval.md,
knowledge-and-memory.md,mcp-tools.md,cli-reference.md, and
configuration.md, plus four standalone diagrams underdocs/diagrams/
(system architecture, indexing, retrieval, doc ingestion). Every
identifier is verbatim from source; no hand-cited line numbers.
README.md/AGENTS.md/CONTRIBUTING.mdpointers rewired to the new
set.
Removed
- Legacy doc set (15 files) and the entire
specs/tree (102 files) —
docs/review-checklist.mdanddocs/release-checklist.mdare 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-agentsnow supports the kilo CLI (Kilo Code): MCP wired
intokilo.json(workspace) or~/.config/kilo/kilo.json(global)
under themcpkey — the opencode-format schema kilo documents
(type: remote+urlunder the SSE default,type: local+
command-array for--stdio). Detection covers thekiloCLI on PATH,
~/.config/kilo/, and a workspacekilo.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 globalregisters
viaclaude mcp add --transport sse --scope user cairn <url>and a
droid install with thedroidCLI 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.--stdiostill opts out; the shared daemon
URL resolution is deduplicated in onedefault_sse_urlhelper. - agy (Antigravity) SSE installs now write the documented remote shape —
mcpServers.cairn.serverUrl— instead of the sharedtype/url
fields. Antigravity does not support legacyurl/httpUrlfields
(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-agentsnow 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;--stdioopts out globally.
Fixed
install-agents --scope globalfor 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-depsno longer
corrupts (and can now self-repair) when multiple Python interpreters share
a machine, andcairn upgrade/ the model downloads show one progress
line instead of streaming pages of installer/downloader output.
Fixed
cairn embed --install-depsno 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/libsilently corrupted when e.g. a 3.11 dev venv and a 3.14
install both ran--install-depsinto it. The mixed dir then failed every
install verification (ImportError: cannot import name '_regex'…) and
pip's--targetskip-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 onsys.path, shadowed by the ABI dir),
and an explicitCAIRN_LIBis 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--pythonto the running interpreter so uv can no
longer resolve wheels for a different ABI than the one running cairn.cairn upgradeno 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-modelandcairn download-rerankerget 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-depsno 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 nextcairn embedimports at
full speed.cairn embed --download-modelgets 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 fromsys.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,whenguard 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 undertests/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/suggestendpoint, 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-cacheand asset URLs carry a mtime version, so a
browser holding an old cachedapp.jsnever 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-kotlinPyPI 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 thecairn._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, theoperator fun invokerewrite) 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-intelwheels 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-intelstays 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'spyjwt[crypto]— has dropped Intel-macOS wheels; the first
per-wheel install test is what surfaced both limits). - The dashboard subsystem is now documented:
cairn dashboardentry 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 dashboardgrows 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
("runcairn build"), never an error page.- MCP tool-call recording —
tool_metricsgainsreq_chars,resp_chars,
and a redactedargs_summary(additive migration; buffered off the hot
path, flushed on clean shutdown), and the server stamps a per-process
CAIRN_SESSIONid 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,/graphlinked 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 —
/workspacesgives 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 intotool_metricswith asourcecolumn (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/healthnever 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_metricsgainstruncated_from_chars/
truncated_to_charsrecorded 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_metricsnow ages out underCAIRN_TOOL_METRICS_MAX_ROWS
(default 50000) and optionalCAIRN_TOOL_METRICS_MAX_AGE_SECONDS,
applied by the recording pipeline's flush transaction — never by the
read-only dashboard. Export:/history.csv|.jsonand/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 intosqlite3; 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_syncstaleness banners) instead of the pre-watcher
boot-only model, andsemantic_searchdocuments itsrerankparameter and
theCAIRN_RERANK_MIN_MARGINauto gate.docs/cli-reference.md: 15 (not 14)
memory subcommands withcairn memory embeddocumented,update --knowledge,config --db, and thebuild --staging/--repomutual
exclusion.docs/configuration.md:CAIRN_WORKERSis clamped to [1, 256]
(not uncapp