- Status: Proposed
- Date: 2026-04-12
Context
CodeLens already has a sound top-level split:
codelens-engineas the code-intelligence enginecodelens-mcpas the harness-facing MCP runtime
The current problem is not top-level architecture failure. The current problem is intra-layer concentration and drift:
- oversized files such as
state.rs,tools/symbols.rs, andembedding/mod.rs - duplicated registries such as LSP defaults in both engine and MCP layers
- retrieval/query policy concentrated inside one tool module
This creates three recurring costs:
- maintenance and review become harder
- AI-generated incremental code is harder to judge for necessity
- behavior drift can occur without an explicit architectural decision
Decision
We will keep the existing two-crate boundary and simplify inside it.
1. Preserve The Two-Crate Split
Keep:
codelens-engine= parsing, indexing, search, graph, semantic backendcodelens-mcp= transport, runtime state, tool exposure, workflow policy
We are explicitly rejecting a ground-up rewrite or a re-merge into one crate.
2. Make Registries Single-Source
Authoritative registries must live in exactly one place.
Immediate target:
- LSP recipes live in engine
lsp/registry.rs - MCP uses engine-derived defaults instead of maintaining its own parallel mapping
Rule:
- no second extension-to-command table is allowed unless the owning ADR explicitly justifies it
3. Split Query Analysis From Tool Handler Code
tools/symbols.rs should stop owning every part of:
- lexical-vs-NL classification
- identifier splitting
- query expansion
- semantic priors
- response shaping
Target shape:
tools/query_analysis.rsor equivalent extracted module- handler file keeps orchestration only
4. Shrink AppState By Responsibility
AppState remains the runtime root, but its internal responsibilities should be extracted into focused units:
- project runtime context/cache
- session/runtime surface state
- analysis artifact/job coordination
- watcher maintenance and health
The goal is not an abstraction explosion. The goal is fewer unrelated responsibilities per file.
5. Prefer Measured Simplification Over New Abstractions
New modules are only justified if they reduce one of:
- duplicated logic
- blast radius
- review difficulty
- state coupling
We explicitly reject interface-heavy layering that adds names without reducing coupling.
Consequences
Positive
- fewer silent drift points
- smaller review surfaces
- clearer ownership boundaries
- easier alignment with harness-oriented skills and agent workflows
Negative
- short-term refactor cost
- temporary churn around imports and test placement
- some historic files will need careful blame-aware extraction
Non-Goals
- rewriting the project around a new framework
- replacing tree-sitter-first retrieval with LSP-first retrieval
- removing profile/surface shaping
- merging all workflow reports into one generic engine layer
Migration Plan
Phase 1
- remove version/path drift from monorepo dependency declarations
- centralize LSP defaults
Phase 2
- extract query analysis from
tools/symbols.rs - keep tests close to the extracted behavior
Phase 3
- extract the smallest low-risk
AppStateslice first - likely candidates: watcher maintenance or project-context cache helpers
Phase 4
- unify candidate fan-out logic between cached and non-cached symbol retrieval
Phase 5
- split semantic backend internals inside
embedding/
Acceptance Signals
- fewer duplicated registries
- smaller top-risk files
- no regression in
cargo checkand targeted MCP tests - documentation can describe one authoritative source for each major runtime registry