All notable changes to this project are documented here.
Forward-direction name-bridge symmetry. Fixes QueryOutgoingImpact returning empty direct_items when the seed symbol_uri was indexed by a different provider than its callees — typically a SCIP-descriptor seed (…#AnalyzeImpact().) whose outgoing edges were recorded in tier-1 form (…#AnalyzeImpact), or vice versa. The reverse direction (QueryBlastRadiusSymbol) already handled this via the callee_name_to_callers name-bridge added in v2.3.2; the forward direction only consulted caller_to_callees by URI-exact match, so any caller-side SCIP/tier-1 mismatch produced zero direct hits even though QueryBlastRadiusSymbol on the same symbol worked. protocol_version stays at 2; this is a pure correctness fix with no wire-shape change.
caller_name_to_callees: HashMap<String, Vec<String>>— forward-direction twin ofcallee_name_to_callers. Populated at all three edge-insertion sites (upsert_file,upsert_file_precomputedSCIP path,upsert_file_precomputedtier-1 back-fill) usingnormalize_callee_name(extract_name(&edge.from_uri))so SCIP descriptor sigils ((),.,#,:) are stripped to a shared identifier key.remove_file_call_edgesdrops caller-side entries symmetrically with the existing callee-side cleanup.outgoing_impact_for's BFS now consults both indexes per hop — URI-exact lookup viacaller_to_calleesplus name-bridge lookup viacaller_name_to_callees— matching the structural shape ofblast_radius_forPhase 2. Regression testoutgoing_impact_name_bridge_for_tier1_caller_uriseeds a SCIP-descriptor caller whose edges were recorded in tier-1 identifier form and asserts the callee surfaces on the wire.
- Rust 1.95 clippy hygiene. Fixed five newly-enforced lints across
query_graph/db.rs,query_graph/module_id.rs, andindexer/symbol_extractor.rs:unnecessary_map_or,unnecessary_sort_by,manual_pattern_char_comparison, and twocloned_ref_to_slice_refs.cargo fmt --allnormalized 19 files touched by v2.3.x work. No behavior change.
Module-level grouping on impact items. Adds module_id: Option<String> to ImpactItem and SemanticImpactItem so consumers whose risk classifier weights cross-module blast (CKB's RecomputeBlastRadius.ModuleCount) get a useful value instead of the conservative zero that the unioned static-plus-LIP set previously collapsed to. protocol_version stays at 2; the field is #[serde(default, skip_serializing_if = "Option::is_none")], so the wire shape stays byte-identical for emitters that don't populate it and deserialises cleanly on pre-v2.3.4 clients.
-
ImpactItem.module_idandSemanticImpactItem.module_id— resolved once at upsert time, stored onFileInput, surfaced on everyImpactItem/SemanticImpactItembuilt byblast_radius_for,blast_radius_for_symbol,blast_radius_batch, andoutgoing_impact_for. Three-tier resolution, first hit wins:- Slice URI prefix —
lip://<manager>/<package>@<version>/...→"<manager>/<package>". Covers mounted dependency slices. - SCIP package descriptor — the first
<manager> <name>pair parsed from any SCIP symbol attached to the file →"<manager>/<name>". Covers every file imported viaupsert_file_precomputedwhose symbols carry real SCIP metadata (scip-go, scip-typescript, scip-rs, etc.). Rejectslocal <id>sentinels and empty-package descriptors. - Manifest walk — upward walk (depth-capped at 12) from the file's directory, looking for a language-appropriate manifest:
Cargo.toml(Rust,[package] name),go.mod(Go,module <path>),package.json(TypeScript / JavaScript / TSX / JSX, top-level"name"),pyproject.toml(Python,[project].nameor[tool.poetry].name),setup.py(Python,name="…"in setup call),pubspec.yaml(Dart, top-levelname:). Parse failures, I/O failures, and unsupported languages (C / C++ / Kotlin / Swift / Java) returnNonerather than propagating.
Unit-tested per parser (
parse_cargo_toml_extracts_crate_name,parse_go_mod_extracts_module_path,parse_package_json_ignores_name_inside_values,parse_pyproject_toml_{project,poetry}_section,parse_setup_py_double_and_single_quotes,parse_pubspec_yaml_{name,ignores_nested_name}, plus Cargo's workspace-only and dependency-section edge cases). Resolver-level tests cover the priority ordering (resolve_prefers_slice_uri_over_scip,resolve_falls_back_to_scip_when_no_slice,resolve_walks_manifest_for_tier1_rust_file,resolve_returns_none_when_unsupported_language_and_no_scip). Integration tests (blast_radius_surfaces_module_id_from_scip_descriptor,blast_radius_surfaces_module_id_from_cargo_toml_walk,outgoing_impact_surfaces_module_id) confirm the field reaches the wire through both RPCs. - Slice URI prefix —
Outgoing-impact symmetry. Adds a single additive RPC so CKB can query the forward direction of the call graph with the same enriched envelope and provenance gating that QueryBlastRadiusSymbol already provides for the reverse direction. protocol_version stays at 2; pre-v2.3.3 daemons reply UnknownMessage.
QueryOutgoingImpact { symbol_uri, depth?, min_score? }→OutgoingImpactResult { result: Option<EnrichedOutgoingImpact> }— forward-direction twin ofQueryBlastRadiusSymbol. BFS walkscaller_to_calleesstarting fromsymbol_uri, splits direct vs. transitive hops, and wraps the static result in an envelope flattened with#[serde(flatten)] static_result: OutgoingImpactStaticsoedges_sourcelives on the inner struct (matching the v2.3.2 shape for blast radius).depthis clamped to1..=8with a default of 8;NODE_LIMIT=200bounds the BFS frontier and tripstruncated: trueon overflow. Semantic enrichment reusesSemanticImpactItem { source: Static | Semantic | Both }: symbol-level embedding is preferred, with file-level embedding as the fallback seed, and static-hit files are taggedBothwhen their URI also appears in the nearest-embedding set. The Bug-D-style#<name>-strip fallback from v2.3.2 Phase 3 is applied symmetrically on the callee side, so tier-1 URIs with nodef_indexentry still resolve to their file URI instead of producing blanksymbol_uri.edges_source: Option<EdgesSource>onOutgoingImpactStaticmirrors blast radius so CKB can apply the sameEdgesSourceEmpty → skip foldprovenance gate. Advertised asquery_outgoing_impactinHandshakeResult.supported_messages; round-trip testsquery_outgoing_impact_round_trips,query_outgoing_impact_is_batchable,outgoing_impact_result_round_tripscover the wire shape, and db-level testsoutgoing_impact_direct_and_transitive+outgoing_impact_phase3_fallback_for_tier1_callee_uricover BFS correctness and the Phase-3 fallback.
CKB testdrive follow-up. Six correctness fixes discovered after v2.3.1 shipped and CKB began consuming EnrichedBlastRadius end-to-end. protocol_version stays at 2; the only schema change moves an existing field between structurally-nested records and is wire-compatible via #[serde(flatten)].
edges_sourcemoved fromEnrichedBlastRadiusontoBlastRadiusResult— so non-enrichedQueryBlastRadius(not justQueryBlastRadiusBatch/QueryBlastRadiusSymbol) carries call-edge provenance.EnrichedBlastRadiusstill surfaces it through#[serde(flatten)] static_result: BlastRadiusResult, so the JSON wire shape is unchanged. Rust callers that field-accessenriched.edges_sourcemust switch toenriched.static_result.edges_source. Round-trip testedges_source_survives_all_response_envelopesasserts the field is emitted in every response variant.
-
Tier-1 back-fill URIs now translate to SCIP descriptor form — same-file and cross-file. v2.3.1 Feature #5 re-ran the tree-sitter tier-1 extractor on disk when a SCIP import carried no call edges, but tier-1 emits fragment URIs in plain-identifier form (
#NewExporter) while scip-go / scip-typescript emit descriptor form (#NewExporter(),#Component.). Every tier-1-emitted caller and callee misseddef_index.get(caller_sym)inblast_radius_forPhase 3, so Phase 4 fell through to the file-level fallback and producedImpactItems with blanksymbol_uri— breaking dedup on the CKB side. The back-fill now builds a same-filedisplay_name → SCIP-urimap (plus URI fragment as secondary key) and, for cross-file callees whose definition lives in another SCIP document, falls back to the globalname_to_symbolsindex (populated at SCIP-import time from each symbol'sdisplay_name, with symmetric cleanup on re-upsert and file removal). Both sides of every tier-1 edge are translated in-place when an unambiguous match exists. Regression teststier1_backfill_translates_caller_uri_to_scip_fragment(same-file) andtier1_backfill_resolves_cross_file_callee_via_name_index(cross-file) seed SCIP-descriptor defs against real on-disk sources and assert everyImpactItemcarries a non-empty, translatedsymbol_uri. -
Path-traversal guard on SCIP document ingestion. scip-go ships documents whose
relative_pathpoints outside the project tree (e.g.../../../../Library/Caches/go-build/…).build_document_uripreviously joined them literally, producing URIs likelip://local//Users/lisa/Work/Projects/CKB/src/../../../../Library/Caches/go-build/…that the daemon's path-based indexer happily ingested.convert_documentnow rejects any document whose net depth falls below the project root under pure string-level normalization (no filesystem access, so the check works on machines other than the one that produced the index). Emitswarning: skipped N SCIP document(s) whose relative_path escapes project_rootwhen any documents are dropped. -
Double
lip://local/prefix incallee_to_callerskeys.SymbolExtractor::lip_uristripped thefile://scheme but not an existinglip://local/prefix. Whenupsert_file_precomputed's tier-1 back-fill replayed the tree-sitter extractor against a file imported with its canonical key (lip://local//<abs>/<rel>), every emitted edge URI was double-prefixed (lip://local/lip://local//<abs>/<rel>#name), making URI-exact BFS lookups impossible inblast_radius_forPhase 2. Fixed by detecting thelip://local/prefix and appending#<name>directly; thefile:/// bare-path branch is unchanged for tier-1 callers that pass raw paths. Confirmed viaLIP_DEBUG_EDGES=1diagnostic trace from a CKB testdrive. -
SCIP-descriptor / tier-1-identifier name-fragment mismatch in
callee_name_to_callers. Tier-1 extractor indexes plain identifiers (SearchSymbols); SCIP descriptors carry suffix sigils (SearchSymbols().,MyField.,Foo:). Phase-2 BFS inblast_radius_fordidextract_name(callee)without stripping the sigils, so cross-provider lookups always missed even when both providers had indexed the same function. Addednormalize_callee_name(fragment)— truncates at the first(, then trims trailing non-identifier chars — and applied it at all fourcallee_name_to_callersinsert sites plus the BFS lookup site, so SCIP and tier-1 callees now share keys. Unit testnormalize_callee_name_strips_scip_descriptor_suffixescovers the six canonical SCIP descriptor shapes. -
Blank
symbol_uriwhen tier-1 back-fill preserves a raw caller URI. After the back-fill resolver falls back toedge.from_uri(bothtranslateandname_to_symbolsmiss for the caller name — e.g. a caller function not captured as a SCIPSymbolInformation),callee_to_callersstores the raw tier-1 URIlip://local//<abs>#<name>.def_indexwas never populated for that URI (it only records SCIP occurrences), so Phase 3 ofblast_radius_forskipped every such caller and Phase 4 fell through to the file-level fallback — producing 100 % blanksymbol_uriin the CKB testdrive against real projects. Phase 3 now derives the file URI by stripping the#<name>fragment whendef_indexmisses and the caller URI has thelip://local/scheme, using the caller URI verbatim assymbol_uri. No double-indexing required. Regression testblast_radius_phase3_fallback_for_tier1_caller_uriimports a caller file with no SCIP symbols (forcing the resolver miss) against an on-disk source whose tier-1 edge walks to a SCIP-indexed target, then asserts theImpactItemcarries the full tier-1 caller URI rather than a blank.
LIP_DEBUG_EDGES=1diagnostic gating.upsert_file_precomputed,blast_radius_forPhase-2 BFS, andwrite_message(wire output) emit focused[lip-debug-edges]traces to stderr when the env var is set. Zero-cost and silent when unset. The wire log now reportshas_edges_source/edges_source_count/body_bytes+ 500-char head instead of a truncated 2 KB tail, so edges_source presence on the wire can be confirmed without scrolling through multi-kilobyte bodies.
CKB import landing fix. Addresses the "lip import --push-to-daemon prints success but every file shows indexed: false" class of bug by making client- and daemon-side URI conventions converge, and by back-filling call edges when SCIP imports carry none. protocol_version stays at 2; every change is either additive or limited to CLI behaviour.
-
RegisterProjectRoot { root: String }— idempotent client message that registers a filesystem root with the daemon. The daemon uses its registered roots to resolve relativelip://local/<rel>URIs against the absolute-form records emitted by the tier-1 indexer and bylip import(lip://local//<abs>/<rel>). Longest matching root wins when multiple are registered. Advertised asregister_project_rootinHandshakeResult.supported_messages; pre-v2.3.1 daemons replyUnknownMessage, in which case clients must send absolute URIs.RegisterTier3Sourcenow auto-registers itssource.project_rootwhen non-empty, so SCIP imports that carry a project root need no second round-trip. -
EdgesSourceprovenance on blast radius results —EnrichedBlastRadiusgainsedges_source: Option<EdgesSource>with four variants (Tier1 | ScipWithTier1Edges | ScipOnly | Empty). Consumers that maintain their own fallback path (e.g. CKB's native SCIP backend) can now detect when LIP has no structural edges for a file and route around us.#[serde(default, skip_serializing_if = Option::is_none)], so the field is invisible to pre-v2.3.1 clients. -
Tier-1 edge back-fill on SCIP imports —
upsert_file_precomputednow falls back to running the tree-sitter tier-1 extractor over the file on disk when the incoming SCIP document has no call edges. Producesedges_source = ScipWithTier1Edgeswhen the fallback succeeds,Emptywhen the file is unreadable or yields no calls. Fills the gap wherescip-goinconsistently emits call edges andscip-clangomits them entirely. -
lip import --verify— after pushing deltas, samples up to 10 documents and round-tripsQueryFileStatus(expectingindexed = true) plus aQueryWorkspaceSymbolsprobe scoped to the file when the document carries an exportedFunction/Class/Interfacedefinition. Exits non-zero on any mismatch so CI catches silent import drops instead of printing success and returning 0. Requires--push-to-daemon.
-
lip importURI scheme — imported documents now uselip://local/<rel>(whenMetadata.project_rootis absent) orlip://local/<abs>/<rel>with the canonical doubled slash (when it is present), replacing the previousfile:///<rel>form that silently failed to match any CKB query. Requires CKB to callRegisterProjectRootfor its workspace root before querying by relative path. -
LipDatabase::canonicalize_uri— every public query- and mutation-surface method now canonicalises its URI argument throughregistered_rootsbefore hitting the input/embedding/def/sym/occ maps, so relative and absolute lip-local forms of the same file resolve to the same record. Non-lip-local URIs (file://…,scip://external, bare paths) are returned unchanged.
-
CKB "printed success but nothing landed" — the combined effect of the import URI change +
RegisterProjectRoot+ daemon-side canonicalisation meanslip importrecords are now discoverable by a CKB client that querieslip://local/<rel>, which was the class of bug reported after v2.3.0. -
Self-echo deadlock on bulk precomputed imports. Each session subscribes to the daemon's push-notification broadcast and, in the drain loop that runs after every response, writes every pending notification back to its client. Every
Delta { Upsert }also emitted anIndexChangedonto that same broadcast — so the session received an echo of its own emission and wrote it as a second frame on top of theDeltaAck. The import loop read one frame per iteration, so frame production ran one frame ahead of consumption; after ~65 deltas the 8 KB macOSAF_UNIXsend buffer filled,write_messageparked mid-frame,read_messagenever ran, and every worker sat idle with both processes at 0 % CPU. Fixed by tagging every broadcast message with the emitting session's id (Notification { source_session: Option<u64>, message: ServerMessage }) and having the drain loop skip envelopes whosesource_sessionmatches its own. Tier 2 upgrades emit withsource_session: Noneso they still reach every session. Regression testdaemon_bulk_precomputed_import_does_not_deadlockpushes 200 precomputed deltas through a single session and fails fast if anyIndexChangedecho reaches the client.
CKB structural-parity bundle. Five additive features so CKB (and any other consumer) can retire its duplicate SCIP parser and query LIP for everything structural. protocol_version stays at 2; every new field is #[serde(default, skip_serializing_if = …)] and every new message is advertised via HandshakeResult.supported_messages, so older clients see no change.
-
Rich symbol metadata —
OwnedSymbolInfonow carriessignature_normalized,modifiers: Vec<String>,visibility: Option<Visibility>+visibility_confidence: Option<u8>,container_name: Option<String>,extraction_tier: ExtractionTier, andmodifiers_source: Option<ModifiersSource>. Tier-1 extractors populate the structural fields for Rust / TypeScript / Python / Swift / Kotlin (extraction_tier = Tier1,modifiers_source = None). The SCIP importer (lip import --from-scip) parses upstream-compatibleenclosing_symbol = 8and derives modifiers via prefix-parse (extraction_tier = Tier3Scip,modifiers_source = PrefixParse). Wire round-trip covered end-to-end bydaemon_tier1_emits_v23_metadataanddaemon_precomputed_preserves_v23_metadata. -
Reference classification —
OwnedOccurrencegainskind: ReferenceKind(Unknown/Call/Read/Write/Type/Implements/Extends) andis_test: bool. Tier-1 classifier insymbol_extractor::classify_ref_kinduses tree-sitter parent-node and field-name lookup — call / method targets →Call, assignment-LHS →Write, otherwiseRead;is_teststamps occurrences from paths under/tests/,_test.rs,_test.py,.spec.ts, etc. SCIP import/export round-trips viaSymbolRole::ReadAccess | WriteAccess | Test(Call has no SCIP equivalent and maps toUnknownon re-export). -
QueryBlastRadiusSymbol { symbol_uri, min_score?: f32 } → BlastRadiusSymbolResult { result: Option<EnrichedBlastRadius> }— single-symbol wrapper aroundblast_radius_for_symbol. Resolves the symbol'sdef_indexentry, runs the same structural BFS + semantic-enrichment loop asQueryBlastRadiusBatch, and returnsNonefor unknown or unindexed symbols so the caller can distinguish "zero impact" from "no data." Safe insideBatchQuery. -
QueryOutgoingCalls { symbol_uri, depth: u32 } → OutgoingCallsResult { edges: Vec<OutgoingCallEdge>, truncated: bool }— forward call-graph traversal. Newcaller_to_callees: HashMap<String, Vec<String>>index mirrors the existing reverse map, populated in bothupsert_fileandupsert_file_precomputedand cleaned inremove_file_call_edges. BFS is depth-clamped to[1, 8]withNODE_LIMIT = 200; thetruncatedflag reports when the cap fired. Safe insideBatchQuery. -
Ranked & filtered workspace symbols —
QueryWorkspaceSymbolsadds three optional filters (kind_filter: Option<Vec<SymbolKind>>,scope: Option<String>,modifier_filter: Option<Vec<String>>);WorkspaceSymbolsResultaddsranked: Vec<RankedSymbol>(parallel tosymbols). Tiered scoring:Exact = 1.0, case-insensitivePrefix = 0.8, case-insensitive substringFuzzy = 0.5— not BM25.MatchTypeis a discriminator only, not a ranking signal; callers sort byscore.rankedisskip_if_empty, and an empty query preserves pre-v2.3 behaviour (emptyranked). Pre-v2.3 clients that pattern-match{ symbols }keep working unchanged. -
OutgoingCallEdge,RankedSymbol,MatchType— new public types inlip_core::query_graph::types. -
Drift guard — the two new client messages (
query_blast_radius_symbol,query_outgoing_calls) are registered in bothsupported_messages()andvariant_tag(); thesupported_messages_covers_all_variantstest now covers them.
LipDatabase::workspace_symbolsnow delegates toworkspace_symbols_ranked; the old signature is preserved for callers that do not need the ranked tier (LSP bridge, MCP adapter, legacy CLI). No behaviour change for existing callers.
-
NearestItem.embedding_model— every nearest-neighbour hit now carries the model name that produced its stored embedding. Field is optional /skip_serializing_if = None; older clients see no change. Populated bynearest_by_vector,nearest_symbol_by_vector, andoutliers. Useful for debugging mixed-model indexes and confirming which model was used for a specific result. -
Function-level blast radius (
QueryBlastRadiusBatch) — semantic enrichment now uses per-symbol embeddings when available. IfEmbeddingBatchhas been called withlip://URIs (function-level chunks),semantic_items[].symbol_uriis populated and results are at function granularity. Falls back to file-level embeddings when no symbol embeddings exist, so the upgrade is transparent. -
ReindexStale— atomic "reindex if stale" operation. Acceptsurisandmax_age_seconds; re-reads from disk only the URIs that are not indexed or whose last-indexed timestamp exceeds the threshold. ReturnsReindexStaleResult { reindexed, skipped }. Passmax_age_seconds = 0to force unconditional reindex. Replaces the manualQueryFileStatus→ReindexFilesrace. -
BatchFileStatus— query index status for multiple files in one round-trip. Equivalent to issuingQueryFileStatusinside aBatch, but without message-per-file overhead. Batchable. ReturnsBatchFileStatusResult { entries: Vec<FileStatusEntry> }. -
QueryAbiHash— stable hex hash (SHA-256) over a file's exported API surface (exported symbol URIs + kinds + signatures, sorted). A change in hash means the public interface changed — safe as a downstream recompilation or re-verification trigger (Kotlin IC model). ReturnsAbiHashResult { uri, hash: Option<String> }. Batchable. -
Tier 1.5 Datalog inference —
LipDatabase::run_tier1_5_inference()runs a fixed-point inference loop applying two rules: (1) if every direct caller of a symbol is at confidence ≥ 80 (Tier 2 / SCIP quality), raise the callee to confidence 65; (2) exported symbols with no local callers are raised by 5 points (capped at 65). Never lowers confidence; never exceeds the Tier 1.5 ceiling, leaving headroom for Tier 2. -
Tier 2 backoff recovery — language server backends now recover from transient crashes with exponential backoff (2–300 s, up to 8 failures) instead of being permanently disabled for the session lifetime.
disabled_*flags are kept for hard failures (binary not installed). ABackoffStatestruct tracksfailure_countandavailable_afterper backend. Tests:backoff_fresh_is_available,backoff_fail_makes_unavailable,backoff_reset_clears_state,backoff_permanent_after_8_failures,backoff_not_permanent_before_8_failures. -
FileStatusEntry— new public struct carrying the same fields asFileStatusResultbut suitable for use insideBatchFileStatusResult. -
QueryBlastRadiusBatch— batch blast radius for all exported symbols in changed files, with optional semantic enrichment via file embeddings. Acceptschanged_file_urisand optionalmin_scorethreshold. Resolves symbols server-side (filtered to Function, Method, Class, Interface, Constructor, Macro), runs structural BFS per symbol, and whenmin_scoreis set, augments results with cosine-similarity neighbours from the file embedding index. Each semantic hit carries asourcefield ("semantic"or"both") so consumers can distinguish certainty tiers. Spec §8.1.1. -
QueryInvalidatedFiles— name-based dependency tracking query. Given a set of changed symbol URIs, returns file URIs that consumed those names externally (Kotlin-IC inspired). Enables symbol-level re-verification without full reindex. -
JournalEntry::UpsertFilePrecomputed— journal variant that persists pre-computed symbols, occurrences, and CPG edges from SCIP imports. Fixes data loss on daemon restart for SCIP-imported files.
-
SCIP proto field numbers —
SymbolInformation.relationships(2→4),kind(4→5),display_name(5→6) aligned with upstream SCIP. Fixes protobuf decode crash (LengthDelimited where Varint expected) when importing any index produced by a spec-compliant SCIP emitter. -
SCIP proto
Relationship.is_override→is_definitionto match upstream field 5 semantics. -
SCIP import pre-computed symbol persistence — Delta handler now routes pre-computed documents through
upsert_file_precomputed, populating sym_cache, occ_cache, def_index, name_to_symbols, and call-edge indexes. Previously, SCIP-imported symbols were silently dropped. -
Journal replay for SCIP imports — pre-computed symbols now survive daemon restart via
UpsertFilePrecomputedjournal entry. -
Merkle stale_files — uses stored
content_hashinstead of hashing empty text for pre-computed files. Fixes infinite re-sync loop. -
file_source_text — falls back to disk read for precomputed
file://URIs. Fixes stream_context, embeddings, and explain-match for SCIP-imported files. -
EndStreamReason::CursorOutOfRangeandEndStreamReason::FileNotIndexed— split the previously-conflatedError + "cursor_out_of_range"emission into two typed reasons. Before, a cursor past EOF and a URI the daemon had never indexed both surfaced asreason: error, error: "cursor_out_of_range"; clients could not distinguish "user gave bad coordinates" from "daemon has nothing for this path." Now:CursorOutOfRange— the file is indexed but the cursor line is outside its range. Error message reports the actual line count.FileNotIndexed— the daemon has no record of the URI. Error message names the URI. Callers should upsert or reindex, then retry.Errorremains for any other stream-terminating failure. Clients should branch on specific reasons first and fall through toErrorfor the rest.
Non-breaking for the happy path (
BudgetReached,Exhausted) and for the free-formerrorstring. Clients that strictly matchedreason == Erroron both failure modes now need one extra arm — all v2.1.x CKB builds ship in lockstep so this lands as a coordinated change.
v2.1 — Streaming context + forward-compat primitives
Streaming
StreamContext { file_uri, cursor_position, max_tokens, model? }— new streaming wire message. Daemon ranks symbols relevant to the cursor and emits oneSymbolInfo { symbol_info, relevance_score, token_cost }frame at a time, terminating with exactly oneEndStream { reason, emitted, total_candidates, error? }frame. Reasons:budget_reached,exhausted,error. Replaces the broken "fetch top-k, locally truncate to prompt budget" pattern with stream-until-full. Spec §9.2.- Relevance ordering (spec §2.3): direct symbol at cursor → callers (from blast-radius CPG walk) → callees / references → related types. Conservative token-cost estimate
ceil((len(signature) + len(documentation)) / 4) + 8per symbol. Daemon does not buffer ahead of the socket;BrokenPipefrom a closing client aborts the ranking walk cleanly.StreamContextis rejected fromBatch/BatchQuery. protocol_versionbumped from1→2inHandshakeResult. Clients can detect streaming support via handshake.lip stream-context <file_uri> <line:col> --max-tokens N [--model M]— new CLI subcommand prints frames as JSON for manual testing.
New primitives
EmbedText { text, model? }— embed an arbitrary text string and return the raw vector. Closes the gap left byEmbeddingBatch(URI-only) andQueryNearestByText(embeds internally but discards the vector). Callers re-ranking with their own scoring (centroid arithmetic, federated nearest-neighbour, lexical-then-semantic re-rank) get the embedding directly instead of building a centroid out of nearest-neighbour seeds. ReturnsEmbedTextResult { vector: Vec<f32>, embedding_model: String }. Not permitted insideBatchQuery(requires async HTTP).RegisterTier3Source { source: Tier3Source }+IndexStatusResult.tier3_sources— expose provenance for Tier 3 ingestion batches (SCIP imports).Tier3Source { source_id, tool_name, tool_version, project_root, imported_at_ms }records what producer generated the symbols and when the daemon accepted them. Re-registering the samesource_idoverwrites in place, refreshingimported_at_ms. The daemon deliberately does no staleness detection: stale Tier 3 symbols remain in the graph at their original confidence until the caller re-imports. Surfacing provenance lets clients decide when to warn a user that imported data has aged (e.g.scip-rust imported 3 days ago).lip import --push-to-daemonnow sends this before streaming SCIP deltas, withsource_id = sha256(tool_name + ":" + project_root).IndexStatusResult.tier3_sourcesis#[serde(default)]; older daemons yield an empty vector. Ack'd withDeltaAck. Not permitted insideBatchQuery(mutation).lip import --no-provenance— opt out of Tier 3 provenance registration for ephemeral or test imports that should not pollute a long-lived daemon'stier3_sourceslist. No effect on the default EventStream-JSON output path.
Forward-compat & capability discovery
HandshakeResult.supported_messages: Vec<String>— handshake response now lists everyClientMessagetypetag this daemon understands. Lets clients probe for an individual message (e.g.stream_context,embed_text) without writing "handshake then pray" code or comparingprotocol_versionintegers. Field is#[serde(default)]; older daemons yield an empty vector, which clients should treat as "fall back toprotocol_version."ServerMessage::UnknownMessage { message_type, supported }— when a client sends a well-formed JSON envelope whosetypetag is unknown, the daemon now replies withUnknownMessage(carrying the tag plus the same supported list as handshake) and keeps the socket open, instead of closing after a generic parseError. Lets forward-compatible clients downgrade gracefully to a supported call instead of reconnecting.ServerMessage::Error { message, code }—code: ErrorCodeis a stable, machine-readable category. Clients branch on this instead of string-matchingmessage.#[serde(default)]; older daemons deserialize asErrorCode::Internal.ErrorCodeenum — small, stable set:unknown_message_type,unknown_model,embedding_not_configured,no_embedding,cursor_out_of_range,index_locked,invalid_request,internal(default). Adding a code is non-breaking; renaming or removing one is breaking.embedding_not_configured— daemon has no embedding service (LIP_EMBEDDING_URLunset).no_embedding— URI has no cached embedding yet; callEmbeddingBatchfirst.unknown_model— the embedding endpoint rejected the requested model. Emitted by the daemon when the HTTP backend returns 404 or a 4xx body matchingmodel_not_found/"unknown model"/"model … not found/invalid/unsupported". Transport, rate-limit, and auth errors stay oninternal— retrying with the same model only makes sense after a real config change. Classification lives indaemon/embedding.rs::classify_http_error.invalid_request— request was well-formed on the wire but used incorrectly (e.g. nestedBatch, orStreamContextinside aBatch). Distinct frominternalso clients can avoid retry loops on caller-side mistakes.
Drift guard
ClientMessage::variant_tag+supported_messages_covers_all_variantstest — exhaustive-match helper plus paired test that fails compilation when a newClientMessagevariant is added without being advertised insupported_messages(). Prevents capability-list drift from silently shrinking the handshake surface.
QueryExpansionhandler contract pinned by a db-level test. The post-embedding ranking is now encapsulated inLipDatabase::query_expansion_terms(query_vec, actual_model, top_k), which the handler calls in one line. A regression that drops the model filter would causequery_expansion_terms_rejects_cross_model_scoring(db.rs) to fail, closing the earlier gap where the fix shipped without a paired assertion.QueryExpansionnow honors the caller's model pin. Previously the handler embedded the query with the requested model but then ranked candidates across all stored symbol embeddings regardless of which model produced them — cross-model cosine scores are not meaningful, so the returned "expansion terms" were effectively noise whenever the index held mixed-model vectors. Handler now captures the actual model returned byembed_textsand passes it through a newmodel_filter: Option<&str>parameter onLipDatabase::nearest_symbol_by_vector, restricting candidates to symbols embedded with the same model.SimilarSymbols(which resolves from a URI's own cached embedding) keeps the old unfiltered behavior by passingNone.
- Library crate renamed from
liptolip-coreon crates.io (name was taken) - All three crates now published:
lip-core,lip-cli,lip-registry - Crates metadata: homepage →
https://lip-sigma.vercel.app, docs linked,rust-version = "1.78", READMEs added - Author email updated to
lisa@nyxcore.cloud
v2.0 — Semantic explainability + model provenance
ExplainMatch { query, result_uri, top_k, chunk_lines, model }— explain whyresult_uriranked as a strong semantic match. Chunksresult_uri's source intochunk_lines-line windows, embeds each in one batch call, cosine-scores each against the query embedding (cached for URI queries; embedded on the fly for text queries), and returns the top-top_kchunks with(start_line, end_line, chunk_text, score). Turns "this file is relevant" into "these specific lines are relevant." Not permitted insideBatchQuery(requires HTTP). ReturnsExplainMatchResult { chunks: Vec<ExplanationChunk>, query_model }.- Model provenance — every
set_file_embeddingandset_symbol_embeddingnow records the model name that produced the vector. The name is supplied by theEmbeddingBatchhandler fromembed_texts's return value, so it reflects the model actually used (not just what was configured).QueryFileStatusnow returnsembedding_model: Option<String>.QueryIndexStatusnow returnsmixed_models: boolandmodels_in_index: Vec<String>— clients can warn users when a model upgrade left the index with mixed-model vectors, making cosine scores unreliable across the boundary. - New wire types:
ExplanationChunk { start_line, end_line, chunk_text, score }. - 1 new MCP tool:
lip_explain_match. - MCP updates:
lip_file_statusresponse now includesembedding_model;lip_index_statusresponse now includesmixed_modelsflag andmodels_in_indexlist with a⚠ MIXED MODELSwarning in text output.
v1.9 — Search precision + server-side aggregation (4 features)
filter: Option<String>onQueryNearest,QueryNearestByText,BatchQueryNearestByText,QueryNearestByContrast,FindSemanticCounterpart,QueryNearestInStore— restrict candidate URIs with a glob pattern before scoring. Patterns containing/are matched against the full path; patterns without are matched against the filename only (e.g."internal/auth/**"or"*_test.go"). Implemented via theglob 0.3crate innearest_by_vector.min_score: Option<f32>on all search calls above — quality gate that drops results scoring below the threshold rather than returning low-confidence noise. Clients can fall back cleanly to FTS instead of surfacing the least-bad result.GetCentroid { uris: Vec<String> }— compute and return the embedding centroid (component-wise mean) of a file set without shipping all raw vectors to the caller. ReturnsCentroidResult { vector: Vec<f32>, included: usize }. Safe insideBatchQuery.QueryStaleEmbeddings { root: String }— report files underrootwhose stored embedding is older than their current filesystem mtime (usesfile_indexed_atvstokio::fs::metadata). Files with noindexed_atrecord are conservatively reported as stale. ReturnsStaleEmbeddingsResult { uris: Vec<String> }. Not permitted insideBatchQuery(requires filesystem I/O).- 2 new db methods:
LipDatabase::centroid(),LipDatabase::file_embeddings_in_root(). - Filter/min_score logic duplicated in
FindSemanticCounterpartandQueryNearestInStoresync and async paths (those use inline scoring rather thannearest_by_vector). - 4 new MCP tools:
lip_nearest/lip_nearest_by_text/lip_nearest_by_contrast/lip_find_counterpart/lip_nearest_in_storegainfilter+min_scoreoptional params;lip_get_centroid,lip_stale_embeddingsare new tools.
v1.7 — Semantic retrieval primitives (6 new wire messages)
QueryNearestByContrast { like_uri, unlike_uri, top_k }— contrastive nearest-neighbour search using vector arithmetic: computesnormalize(embed(like) − embed(unlike))then finds thetop_kfiles most similar to that direction. Both URIs must have cached embeddings. ReturnsNearestResult. Safe insideBatchQuery.QueryOutliers { uris, top_k }— return thetop_kfiles fromuristhat are most semantically dissimilar from the rest of the group. Uses leave-one-out mean cosine similarity: files with the lowest mean similarity to peers are returned first. ReturnsOutliersResult { outliers: Vec<NearestItem> }. Safe insideBatchQuery.QuerySemanticDrift { uri_a, uri_b }— compute the cosine distance1 − similaritybetween two stored embeddings. Range[0.0, 2.0]. ReturnsSemanticDriftResult { distance: Option<f32> }. Safe insideBatchQuery.SimilarityMatrix { uris }— compute all pairwise cosine similarities for a list of URIs in one call. URIs without cached embeddings are silently excluded. ReturnsSimilarityMatrixResult { uris: Vec<String>, matrix: Vec<Vec<f32>> }. Safe insideBatchQuery.FindSemanticCounterpart { uri, candidates, top_k }— given a source URI and a pool of candidates, return thetop_kcandidates most semantically similar to the source. Finds test files that cover a changed implementation even when naming conventions differ. ReturnsNearestResult. Safe insideBatchQuery.QueryCoverage { root }— report embedding coverage under a filesystem path. Shows the fraction of indexed files that have embeddings, broken down by directory. ReturnsCoverageResult. Safe insideBatchQuery.- 6 new MCP tools:
lip_nearest_by_contrast,lip_outliers,lip_semantic_drift,lip_similarity_matrix,lip_find_counterpart,lip_coverage.
v1.8 — Higher-order semantic analysis (6 new wire messages)
FindBoundaries { uri, chunk_lines, threshold, model }— detect semantic boundaries within a file by splitting it intochunk_lines-line windows, embedding each in one batch HTTP call, and returning positions where the cosine distance between adjacent windows exceedsthreshold. Defaults: 30 lines, 0.3 threshold. ReturnsBoundariesResult { uri, boundaries: Vec<BoundaryRange> }. Not permitted insideBatchQuery(requires HTTP).SemanticDiff { content_a, content_b, top_k, model }— measure how much the meaning of a file changed between two versions. ReturnsSemanticDiffResult { distance, moving_toward }:distanceis the cosine distance between the two content embeddings;moving_towardis thetop_knearest files tonormalize(new − old), naming the concepts the content moved toward. Not permitted insideBatchQuery(requires HTTP).QueryNearestInStore { uri, store, top_k }— nearest-neighbour search against a caller-providedHashMap<String, Vec<f32>>. Enables cross-repo federation: export embeddings from each root viaExportEmbeddings, merge the maps, then search across all repos in one call. The query URI must have a cached local embedding. ReturnsNearestResult. Safe insideBatchQuery.QueryNoveltyScore { uris }— quantify how semantically novel a set of files is relative to the existing codebase. For each URI finds its nearest neighbour outside the input set and returns1 − similarityas that file's novelty score. ReturnsNoveltyScoreResult { score: f32, per_file: Vec<NoveltyItem> }, sorted by descending novelty. Safe insideBatchQuery.ExtractTerminology { uris, top_k }— extract the domain vocabulary most semantically central to a set of files. Computes the centroid of the input files' embeddings, then scores each symbol by its embedding's proximity to that centroid. ReturnsTerminologyResult { terms: Vec<TermItem> }. Requires symbol embeddings — callEmbeddingBatchwithlip://URIs first. Safe insideBatchQuery.PruneDeleted— remove index entries (including embeddings) for files that no longer exist on disk. On repos with high churn, ghost embeddings accumulate and pollute nearest-neighbour results. FiresIndexChangedafter removal. ReturnsPruneDeletedResult { checked, removed }. Not permitted insideBatchQuery(requires filesystem I/O).- 6 new MCP tools:
lip_find_boundaries,lip_semantic_diff,lip_nearest_in_store,lip_novelty_score,lip_extract_terminology,lip_prune_deleted. - New wire types:
BoundaryRange,NoveltyItem,TermItem,DirectoryCoverage(v1.7). - Bumped
recursion_limitto 512 inlip-clito accommodate the expandedjson!manifest.
ReindexFiles { uris }— force a targeted re-index of specific file URIs from disk, bypassing the directory scan. The daemon reads each file, detects its language from the URI, and callsupsert_file. Useful when the client knows exactly which files changed out-of-band (selective git checkout, build artifact regeneration). ReturnsDeltaAck. Not permitted insideBatchQuery.Similarity { uri_a, uri_b }— pairwise cosine similarity of two stored embeddings. ReturnsSimilarityResult { score: Option<f32> }—Nonewhen either URI has no cached embedding. Routeslip://URIs to the symbol embedding store andfile://URIs to the file embedding store. Safe insideBatchQuery.QueryExpansion { query, top_k, model }— embedquery, find thetop_knearest symbols in the symbol embedding store, and return their display names as expansion terms. Designed for CKB's compound-search path: expand a short query string into related symbol names before runningQueryWorkspaceSymbols. RequiresLIP_EMBEDDING_URL. ReturnsQueryExpansionResult { terms }. Not permitted insideBatchQuery(requires async HTTP).Cluster { uris, radius }— groupurisby embedding proximity within a cosine-similarity radius. Uses greedy single-link assignment: each URI is placed in the first existing group containing a member with similarity ≥radius, or starts a new group. URIs without a cached embedding are silently excluded. ReturnsClusterResult { groups }. Not permitted insideBatchQuery(requires a coupling pass over embeddings that may trigger HTTP for missing vectors).ExportEmbeddings { uris }— return the raw stored embedding vectors forurisasExportEmbeddingsResult { embeddings: HashMap<String, Vec<f32>> }. URIs with no cached vector are omitted. Routeslip:///file://by prefix. Safe insideBatchQuery.
BatchQueryNearestByText— embed N query strings in a single round-trip and return one nearest-neighbor list per query. Replaces N sequentialQueryNearestByTextcalls used by CKB's compound search operations.QueryNearestBySymbol— find symbols similar to a given symbol URI. The daemon embeds the symbol's text (display_name + signature + doc) on demand and searches against a new per-symbol embedding store.EmbeddingBatchnow routeslip://URIs tosymbol_embeddingsandfile://URIs tofile_embeddings.BatchAnnotationGet— retrieve an annotation key for multiple symbol URIs under a single db lock. Safe insideBatchQuery. Replaces N sequentialAnnotationGetcalls used by CKB's agent-lock check at change time.IndexChangedpush notification — emitted to all active sessions after everyDelta::Upsertvia the existing broadcast channel. Carriesindexed_filescount andaffected_uris. Enables precise cache invalidation without pollingQueryIndexStatus.Handshake/HandshakeResult— clients sendHandshake { client_version }on connect; daemon replies withdaemon_version(semver) andprotocol_version(monotonic integer, currently1). Version drift between independently updated daemon and clients is now detectable at connect time rather than producing silent bad results.--managedflag (lip daemon start --managed) — spawns a background watchdog that polls the parent process every 2 s and callsstd::process::exit(0)when the parent has exited. Designed for IDE integrations (CKB, VS Code extension) that manage the daemon as a subprocess.
EmbeddingBatchURI routing:lip://URIs are now stored insymbol_embeddings(new field onLipDatabase);file://URIs continue to usefile_embeddings. The response format is unchanged.
textDocument/typeDefinitionin all 4 Tier 2 backends — rust-analyzer, typescript-language-server, pyright-langserver/pylsp, and dart language-server now calltypeDefinitionfor each symbol after the hover pass. When the response points to a different file, anOwnedRelationship { is_type_definition: true, target_uri }is attached to the symbol. This gives LIP a cross-file type dependency graph — the blast-radius engine can now identify all symbols whose type isFoowhenFoo's definition changes.textDocument/inlayHintsin rust-analyzer backend — afterdocumentSymbol, the rust-analyzer backend fetches all Type-kind inlay hints for the file. Each inferred local variable binding that isn't already exposed bydocumentSymbolbecomes a newVariablesymbol withsignature: "name: InferredType". These are indexed atlip://local/<path>#<name>@<line>:<col>URIs. SCIP indexers do not capture local variable types; this is additive coverage.- SCIP signature extraction —
lip import --from-scipnow extracts type signatures from SCIP documentation. SCIP indexers (scip-rust, scip-typescript, scip-java, …) place the rendered signature asdocumentation[0]. The importer now splits this correctly:doc[0]→OwnedSymbolInfo.signature, remaining entries →OwnedSymbolInfo.documentation. A keyword heuristic handles single-entry arrays. Imported symbols now have their type signatures populated rather thanNone.
- Tier 2 confidence score: 70 → 90 across all 4 backends (rust-analyzer, typescript-language-server, pyright-langserver/pylsp, dart language-server). Aligns with spec §3.3 ("score 51–90") and the roadmap v1.2 target. SCIP imports already used 90; Tier 2 was incorrectly lower.
LipDatabase::upgrade_file_symbolsconfidence floor — upgrades now apply only whenincoming.confidence_score >= existing.confidence_score. A racing Tier 2 job can no longer silently downgrade a symbol that was previously pushed at a higher confidence (e.g. a SCIP import with--confidence 95). The floor also propagatesrelationshipsfrom incoming upgrades.pub(super) file_uri_to_lip_uriextracted as a shared helper inrust_analyzer.rs, re-exported by the other three Tier 2 backends.
- ABI surface fingerprinting (
is_exportedfield onOwnedSymbolInfo) — formal exported-symbol tracking per language. Rust:pubkeyword; TypeScript:exportstatement; Python/Dart: non-underscore name convention.file_api_surface()now filters byis_exportedinstead of heuristics. - Function-level blast radius —
blast_radius_fornow emits oneImpactItemper distinct caller symbol, not one per file. Enables per-function impact analysis when multiple callers in the same file depend on the changed symbol.direct_itemsandtransitive_itemsare deduplicated by(file_uri, symbol_uri). - Kotlin-IC name consumption index —
LipDatabasetracks which external display-names each file references (file_consumed_names: HashMap<String, HashSet<String>>). New query:files_consuming_names(&[name])returns files that must be re-verified when a symbol is renamed or deleted. Matches Kotlin's incremental compilation invalidation model. - SCIP CI batch layer —
lip import --from-scipextended with--push-to-daemon <socket>and--confidence <1–100>. Streams each SCIP document as aClientMessage::Deltadirectly to a running daemon, enabling nightly CI to push compiler-accurate symbols into the live graph without a restart. - Semantic embedding support — new subsystem for dense vector search:
ClientMessage::EmbeddingBatch { uris, model }— batch file embeddings via any OpenAI-compatible HTTP endpoint. Already-cached vectors are returned without a network call; new source upserts invalidate stale embeddings. Configure withLIP_EMBEDDING_URLandLIP_EMBEDDING_MODEL.ClientMessage::QueryNearest { uri, top_k }— find thetop_kmost similar files touriby cosine similarity of stored embedding vectors.ClientMessage::QueryNearestByText { text, top_k, model }— embedtexton the fly and run cosine search. Useful for "find files related to authentication" queries.ServerMessage::EmbeddingBatchResult,NearestResult,NearestItem— corresponding response types.- New
daemon::embedding::EmbeddingClientmodule — thin async HTTP wrapper withfrom_env()andembed_texts().
- Index and file observability — daemon health endpoints for
ckb doctorintegration:ClientMessage::QueryIndexStatus→ServerMessage::IndexStatusResult— indexed file count, pending embedding count, last upsert timestamp (ms), configured embedding model.ClientMessage::QueryFileStatus { uri }→ServerMessage::FileStatusResult— per-file indexed/has_embedding/age_seconds.
- 5 new MCP tools:
lip_embedding_batch,lip_index_status,lip_file_status,lip_nearest,lip_nearest_by_text.
file_api_surface()filter — replaced_-prefix heuristic +SymbolKindcheck withs.is_exportedfield (set by extractors at parse time and by Tier 2 backends via signature prefix).blast_radius_forPhase 3/4 —sym_impacts: HashMap<String, (String, u32)>replaced withsym_items: Vec<(String, String, u32)>(one entry per caller symbol).direct_dependents/transitive_dependentsstill count unique files for backwards compatibility.LipDatabase::upsert_file— additionally recordsfile_indexed_attimestamp and invalidates stalefile_embeddingson source change.LipDatabase::remove_file— clearsfile_consumed_names,file_embeddings,file_indexed_atfor the removed URI.
- Slice mounting —
db.mount_slice()loads a pre-builtOwnedDependencySliceinto the daemon graph at Tier 3 confidence (score=100). Symbols are visible toworkspace_symbols,similar_symbols,symbol_by_uri, and blast-radius lookups. Idempotent: re-mounting the same package replaces prior symbols without accumulation. ClientMessage::LoadSlice— new wire message; session handler logs package name and symbol count on mount.lip fetch --mount— downloads a slice and immediately loads it into a running daemon in one command.lip_load_sliceMCP tool — exposes slice mounting to AI agents.- LSP bridge tests — first test coverage for the bridge: seq counter monotonicity,
CARGO_PKG_VERSIONversion propagation, u64 type invariant. - 8 new db tests — slice mounting: workspace_symbols visibility, Tier 3 confidence, idempotency, def_index population, similar_symbols search, re-mount replace-not-append.
- Workspace metadata —
[workspace.package]in rootCargo.tomlwith author (Lisa Welsch <lisa@tastehub.io>), homepage, repository. All crates inherit via.workspace = true. keywordsandcategorieson all three crates for crates.io discoverability.LICENSEfile (MIT) at repo root.CHANGELOG.md(this file).- CI publish job — triggered on
v*tag push after release builds pass; publisheslip,lip-cli,lip-registryto crates.io in dependency order. RequiresCARGO_REGISTRY_TOKENin GitHub Secrets.
- LSP bridge
did_changehardcodedseq: 0— now callsself.next_seq(). - LSP bridge version was hardcoded
"0.1.0"in ManifestRequest and ServerInfo — nowenv!("CARGO_PKG_VERSION"). - License corrected from Apache-2.0 to MIT across all Cargo.toml files and README.
docs/user/cli-reference.md— addedquery similar,query stale-files,fetch --mount,slice --pip, fullannotatesubcommand docs, updated MCP tools list.
- Dart Tier 2 backend —
dart language-server --protocol=lspwired into the Tier 2 manager. Symbols in.dartfiles are upgraded to confidence 70–90 on save. lip query similar— trigram (Jaccard 3-gram) fuzzy search across all symbol names and documentation. Returns ranked hits with score ≥ 0.2.lip query stale-files— Merkle sync probe. Sends[(uri, sha256_hex)]pairs; daemon returns stale/unknown URIs in one round-trip. Clients use this on reconnect to send only changed deltas.lip annotate search— workspace-wide annotation search by key prefix (e.g.lip:fragile,agent:, empty string for all).lip slice --pip— indexes pip-installed packages from the active Python environment usingpip list+pip show.lip fetch --mount— after downloading a slice, optionally sendsLoadSliceto a running daemon in a single command.ClientMessage::LoadSlice— new wire message to mount a pre-builtOwnedDependencySliceinto the daemon graph at Tier 3 confidence (score=100). Idempotent: re-mounting the same package replaces prior symbols.lip_load_sliceMCP tool — exposes slice mounting to AI agents.lip_annotation_workspace_listMCP tool — workspace-wide annotation search for AI agents.lip_stale_filesMCP tool — Merkle sync probe for AI agents.- Annotation expiry —
expires_msfield is now enforced inannotation_get,annotation_list, andall_annotations.purge_expired_annotations()sweeps stale entries on startup. - Tier 3 confidence (score=100) — all symbols built by
lip sliceare stamped at 100 rather than inheriting the Tier 1 score of 30. - FlatBuffers schema v1.1.0 (
schema/lip.fbs) — extended with the full IPC query layer:BlastRadiusResult,ImpactItem,RiskLevel,SimilarSymbol, all request/response tables,FileHashEntry,QueryStaleFiles,StaleFilesResult.
- LSP bridge
did_changewas hardcodingseq: 0on every change notification instead of incrementing the monotonic counter. This caused DeltaAck sequence tracking to desync. Now callsself.next_seq(). - LSP bridge version was hardcoded as
"0.1.0"in both the ManifestRequest and theinitializeServerInfo response. Now reads fromenv!("CARGO_PKG_VERSION").
LipDatabase—symbol_by_uri,workspace_symbols, andsimilar_symbolsnow search mounted slice symbols in addition to tracked source files.- Roadmap in
LIP_SPEC.mdxupdated from stale v0.x checklist to accurate v1.1 shipped list + v1.2/v1.3/v2.0 future items. README.md— Dart Tier 2 row updated to ✓, MCP tools table updated,lip slice --pipadded.docs/user/cli-reference.md—query similar,query stale-files,fetch --mount,slice --pip, and fullannotatesubcommand docs added.
- Blast-radius indexing — CPG call edges stored and queried.
blast_radius_forcombines file-level reverse-dep BFS with symbol-level CPG BFS for precise impact analysis. lip push— publishes a dependency slice to the registry via HTTP PUT.- Dart Tier 1 — tree-sitter-dart grammar;
dart_symbols,dart_occurrences,dart_callsextractors. - Docker image for
lip-registry— multi-stage Alpine build, scratch runtime. lip_similar_symbolsMCP tool — trigram fuzzy search exposed to AI agents.lip_batch_queryMCP tool — multiple queries in one round-trip.- Workspace annotation search (
AnnotationWorkspaceList) — scan all symbols by key prefix. - SCIP import (
lip import --from-scip) — bootstraps the graph from a CI-generated SCIP index at confidence 90. - SCIP export (
lip export --to-scip) — snapshots the live graph back to SCIP format. - LSP bridge (
lip lsp) — standard LSP server (stdio) backed by the LIP daemon. SupportstextDocument/definition,textDocument/references,textDocument/hover,workspace/symbol,textDocument/documentSymbol. - WAL journal with compaction and full replay on daemon startup.
- Filesystem watcher — OS-native per-file notifications trigger incremental re-index on out-of-band changes.
- Tier 2 backends: rust-analyzer, typescript-language-server, pyright-langserver, with graceful degradation when binary absent.
- Registry client + cache — content-addressable local cache; federated fetch from multiple registry URLs.
BatchQuery— N queries under a single db lock acquisition.
| Language | Tier 1 | Tier 2 |
|---|---|---|
| Rust | ✓ | ✓ rust-analyzer |
| TypeScript | ✓ | ✓ typescript-language-server |
| Python | ✓ | ✓ pyright-langserver |
| Dart | ✓ | — (added Tier 2 in v1.1) |