Cha

1.12.0 - 2026-04-28

v1.12.0 Apr 28 2026 at 13:57 UTC

Added

  • SymbolIndex — structural view of a file, cached separately from SourceModel. New type in cha-core::model carrying the fields consumers like cha deps, LSP workspace-symbols, and future cha summary all share — class/function names + signatures + positions + type_aliases — without per-function-body data (complexity, body hash, TypeRef origin, cognitive, chain depth etc. stay in SourceModel).
    • ProjectCache::{get,put}_symbols store to symbols/{chash}.bin, mirrored independently of parse/{chash}.bin. Same env_hash mechanism invalidates both on parser code changes.
    • cached_symbols(path) is a new warm fast path that skips SourceModel deserialisation entirely — symbols/{chash}.bin is roughly 10% the size of parse/{chash}.bin.
    • cached_parse now populates both caches on every fresh parse, so the two views are always in lockstep.
    • lvgl src/ warm benchmarks (379 files): deps --type imports 1.28s → 38ms (34×), --type classes 1.30s → 56ms (23×), --type calls 1.30s → 48ms (27×). Edge counts unchanged vs. pre-migration (1351/142/8109).
    • cha-cli/src/c_oop_enrich grows a enrich_c_oop_symbols / attribute_methods_by_name_from_symbols pair alongside the existing SourceModel functions. Shared attribute_one_raw keeps attribution rules single-sourced; build-index / write-back are deliberate parallel code paths because the two storage types have to stay independent.
    • cha-cli/src/parse_cache.rs (new module) hosts both cached_parse and cached_symbols.
  • C++ parser now handles ClassName::method() out-of-class definitions, namespaces, and templates. Three gaps in the previous CppParser have been closed:
    • void Foo::bar() {...} (and ::global(), A::B::c(), destructors Foo::~Foo(), operators Foo::operator+()) was silently dropped — find_func_name_node only accepted bare identifier declarators. It now also unwraps qualified_identifier, destructor_name, and operator_name.
    • Out-of-class method definitions now attribute to their owning same-file class: void Foo::bar() bumps ClassInfo::method_count on Foo and flips has_behavior. Cross-file attribution still runs through cha-cli::c_oop_enrich.
    • namespace_definition, linkage_specification (extern "C" { ... }), and template_declaration are now explicitly matched in the top-level dispatch (previously fell through to the generic recursion arm) — same observable behaviour, but the nesting constructs are now a stable hook rather than an accidental default-case artefact.
    • C++-specific declarator helpers moved to a new cha-parser/src/cpp.rs so c_lang.rs stays below the large_file gate.
  • SourceModel.type_aliases now populated for Rust, TypeScript, Python, and Go (previously all four returned empty vec![] with parser-side TODOs). Each parser recognises its language's alias form and records (alias, rhs) pairs: Rust type X = Y; / pub type X<T> = Y;, TypeScript type X = Y; / export type X<T> = Y;, Python 3.12+ type X = Y and pre-3.12 X: TypeAlias = Y, Go type X = Y (only the true alias form — type X Y defined types are excluded). Plain Python X = Y assignments remain unclassified (too ambiguous). Shared extraction lives in a new cha-parser/src/type_aliases.rs module so per-language files stay below the large_file gate.

Changed

  • boundary_leak detector migrated to ProjectIndex. The three smells it emits (abstraction_boundary_leak, return_type_leak, test_only_type_in_production) previously parsed the whole project a second time — the codebase noted a "cached model occasionally drops typedef aliases" concern with root cause TBD. v1.11.0's binary-mtime cache keying removed the suspected root cause, and a new cache::tests::cache_roundtrip_preserves_type_aliases unit test makes the invariant a testable one. boundary_leak::detect now takes &ProjectIndex and shares the same parse pass as anemic_domain_model, typed_intimacy, module_envy, and friends. Verified against lvgl's src/ tree: 155 findings before = 155 findings after (abstraction_boundary_leak: 154, return_type_leak: 1). Completes roadmap S8.infra.4.

Fixed

  • C++: template specialisation methods attribute to the right class. template<> void Foo<int>::bar() used to drop on the floor because the qualifier Foo<int> (a template_type node) didn't match the stored class name Foo. attach_to_class now strips trailing <...> template arguments before matching, so out-of-class specialisations attribute correctly. Same stripping applies to any declaration whose declarator surfaces Foo<...> as the owning scope.
  • C++: real inheritance (class Derived : public Base) now recognised. extract_class consults the base_class_clause child and pulls the first type_identifier (or template_type's underlying name) as parent_name. Falls back to the first-field heuristic only when no base clause is present, so legacy C struct-embedding cases still work. Also fixes the class-name extraction for templated classes so template<typename T> class Foo {...} stores "Foo" instead of "Foo<T>".
  • C++: reference-return methods no longer vanish. const int& Foo::get() and similar reference_declarator-wrapped definitions used to be silently dropped because tree-sitter-cpp's reference_declarator has no declarator field — the declarator walker returned None. Both find_func_name_node (c_lang) and descend_to_qualified_identifier (cpp) now fall back to the first named child when the field is absent. Same fix path covers reference-return + qualified (const T& Foo::bar()) so class attribution still works. 6 additional regression tests (reference/pointer return types, multi-method attribution, constructor, extern "C", const member) added in cha-parser/tests/cpp_enhancements.rs (14 total).