Architecture
Cha is a Rust workspace with seven crates. The dependency direction is fixed: cha-core does not depend on cha-parser (it only touches cha-parser's output via the traits in cha-core/src/plugin.rs), cha-cli depends on cha-core, and cha-plugin-sdk depends on neither. Don't reverse this direction.
Crate map
flowchart TB
xtask["xtask<br/><i>ci / release</i>"]
cli["cha-cli<br/><i>binary</i>"]
core["cha-core<br/><i>analysis</i>"]
lsp["cha-lsp<br/><i>server</i>"]
parser["cha-parser<br/><i>tree-sitter wrappers</i>"]
sdk["cha-plugin-sdk<br/><i>guest-side, no host deps</i>"]
xtask -.-> cli
xtask -.-> core
cli --> core
lsp --> core
parser --> core
sdk -. WASM .-> core
classDef host fill:#e8f5e9,stroke:#2e7d32,color:#1b5e20;
classDef tool fill:#fff8e1,stroke:#f57f17,color:#5d4037;
classDef guest fill:#e3f2fd,stroke:#1565c0,color:#0d47a1,stroke-dasharray:5 3;
class core,cli,lsp,parser host;
class xtask tool;
class sdk guest;
| Crate | Lives in | Owns |
|---|---|---|
cha-core | cha-core/ | Plugin trait, Finding / SourceModel / SymbolIndex model, registry, reporters (terminal/JSON/SARIF/HTML/LLM), WASM runtime, two-level cache. |
cha-parser | cha-parser/ | Tree-sitter parsers for Python, TypeScript / TSX, Rust, Go, C, C++. Produces SourceModel and SymbolIndex. |
cha-cli | cha-cli/ | Binary. Subcommands: analyze, parse, baseline, fix, deps, layers, hotspot, trend, calibrate, preset, plugin, lsp, etc. |
cha-lsp | cha-lsp/ | LSP server library + binary entry. Diagnostics, code actions, code lens, hover, inlay hints, semantic tokens, workspace diagnostics. |
cha-plugin-sdk | cha-plugin-sdk/ | Guest-side library + plugin! macro. Compiles to wasm32-wasip2. No cha-core dependency. |
xtask | xtask/ | cargo xtask automation: ci, test, lint, analyze, bump, release, publish, docgen-cli, docs-check, i18n-check. |
vscode-cha | vscode-cha/ | VS Code extension. Auto-downloads matching cha binary on first launch. |
Data flow
flowchart LR
src["source files"]
parser["cha-parser"]
model[("SourceModel")]
cfg["config TOML"]
analyze["Plugin::analyze"]
findings["Vec<Finding>"]
cache[("L1 mem + L2 bincode")]
src --> parser --> model
model --> analyze
cfg --> analyze
analyze --> findings
model --> cache
cache -.cached?.-> analyze
classDef store fill:#fff3e0,stroke:#e65100,color:#bf360c;
classDef proc fill:#e8f5e9,stroke:#2e7d32,color:#1b5e20;
class model,cache store;
class parser,analyze proc;
SourceModel is the single shared format. Every plugin sees the same &AnalysisContext { file, model, config }. The model is parsed once, hashed into the cache key, and shared across all plugin invocations on the same file.
WASM plugins go through one extra hop: a host adapter in cha-core::wasm serialises AnalysisInput (a subset of AnalysisContext that fits the WIT interface) and crosses the WASM boundary. Inside the guest, cha-plugin-sdk decodes it back into idiomatic Rust types.
The Plugin trait
Built-in detectors implement cha_core::Plugin:
#![allow(unused)] fn main() { pub trait Plugin: Send + Sync { fn name(&self) -> &str; fn smells(&self) -> Vec<String>; fn description(&self) -> &str; fn analyze(&self, ctx: &AnalysisContext) -> Vec<Finding>; } }
WASM plugins implement cha_plugin_sdk::PluginImpl — a parallel trait with the same shape, modulo string instead of &str returns (WIT requirement). The host bridge in cha-core::wasm lets a PluginImpl impl participate in the same registry as native ones.
Caching
Two layers, both in cha-core::cache:
- L1: in-memory
DashMap<PathBuf, CachedResult>. Lifetime: a singlecha analyzeinvocation. - L2: bincode files under
.cha/cache/. Cache key is(file mtime, file size, plugin set hash, config hash). mtime fast-path skips parsing entirely when nothing's moved.
Plugin set hash includes installed .wasm files — installing or reinstalling a plugin invalidates anything that plugin touched, automatically.
When to extend each crate
| You want to... | Touch |
|---|---|
| Add a built-in smell | cha-core/src/plugins/ + register in cha-core/src/registry.rs |
| Support a new language | cha-parser/src/<lang>.rs + map in cha-parser/src/lib.rs |
| Add a CLI subcommand | cha-cli/src/<subcommand>.rs + wire in cha-cli/src/main.rs |
| Expose new SDK functionality to WASM plugins | Update wit/cha-plugin.wit, regenerate bindings, implement host adapter in cha-core/src/wasm.rs, expose in cha-plugin-sdk/src/lib.rs |
| Add an LSP capability | cha-lsp/src/lib.rs |
See also
- Writing a smell
- Plugin development (host-side trait + WASM SDK)
PluginandPluginImplsource