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;
CrateLives inOwns
cha-corecha-core/Plugin trait, Finding / SourceModel / SymbolIndex model, registry, reporters (terminal/JSON/SARIF/HTML/LLM), WASM runtime, two-level cache.
cha-parsercha-parser/Tree-sitter parsers for Python, TypeScript / TSX, Rust, Go, C, C++. Produces SourceModel and SymbolIndex.
cha-clicha-cli/Binary. Subcommands: analyze, parse, baseline, fix, deps, layers, hotspot, trend, calibrate, preset, plugin, lsp, etc.
cha-lspcha-lsp/LSP server library + binary entry. Diagnostics, code actions, code lens, hover, inlay hints, semantic tokens, workspace diagnostics.
cha-plugin-sdkcha-plugin-sdk/Guest-side library + plugin! macro. Compiles to wasm32-wasip2. No cha-core dependency.
xtaskxtask/cargo xtask automation: ci, test, lint, analyze, bump, release, publish, docgen-cli, docs-check, i18n-check.
vscode-chavscode-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&lt;Finding&gt;"]
    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 single cha analyze invocation.
  • 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 smellcha-core/src/plugins/ + register in cha-core/src/registry.rs
Support a new languagecha-parser/src/<lang>.rs + map in cha-parser/src/lib.rs
Add a CLI subcommandcha-cli/src/<subcommand>.rs + wire in cha-cli/src/main.rs
Expose new SDK functionality to WASM pluginsUpdate 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 capabilitycha-lsp/src/lib.rs

See also