Cha
Releases
1.19.0 - 2026-05-22
v1.19.0
May 22 2026 at 18:34 UTC
Internal: Dogfooding cleanup, phase 4
Continues v1.18.0's pass: hardcoded thresholds and keyword lists become plugin config; cha fix stops hardcoding smell names; the last two text-scanning detectors switch to AST queries.
Added
Plugin::try_fix(finding, ctx) -> Option<Patch>— every plugin can now contribute auto-fixes.cha fixwalks all enabled plugins and asks each one. Adding fix support for a new smell is one trait override, not a host-sideif smell_name == ...patch.cha_core::Patch/cha_core::TextEdit— public byte-range edit types for plugin authors. Edits within a single finding are applied in reverse byte-offset order.DesignPatternAdvisorconfig — 8 magic-number thresholds (strategy_min_arms,state_min_arms,builder_min_params,builder_alt_min_params,builder_alt_min_optional,null_object_min_count,template_min_self_calls,template_min_methods) and 2 keyword lists (type_field_keywords,state_field_keywords) are now overridable via[plugins.design_pattern].GodClassAnalyzer::min_tcc— Tight Class Cohesion threshold (Lanza-Marinescu's 1/3) is configurable.cache.rswalker skip-dirs — extended from{target, node_modules, dist}to also skipbuild,out,__pycache__,venv,.venv,vendor. Reduces unnecessary.cha.tomldiscovery work in polyglot repos.
Fixed
switch_statement/message_chain: replaced bespoke text scanners (find_switch_keyword,walk_chainand ~140 lines of hand-rolled tokenization) with tree-sitter queries againstswitch_statement/match_expression/field_expression/member_expression/selector_expression. lvgl baseline counts unchanged (139 / 61), but keyword positions are now sourced from the AST. No more false positives on keywords inside strings.inappropriate_intimacyimport resolution: extension probe expanded from{.ts, .tsx, .rs}to also include.py,.go,.cpp,.cc,.cxx,.c,.h,.hpp,.hxx,.js,.jsx,.mts,.cts. Sibling-file lookups in non-JS/Rust projects no longer silently fail.calibrate.rstable rendering: 3 metric labels were hardcoded across 3 separateprintlnblocks. Adding a calibration metric now means one entry in a(label, samples)array.
Changed
cha fixdelegates toPlugin::try_fixfor every finding — no morefilter(|f| f.smell_name == "naming_convention")on the host. Existing behavior preserved (NamingAnalyzer fixesnaming_conventionPascalCase violations); other plugins returnNoneuntil they opt in.
1.18.0 - 2026-05-22
v1.18.0
May 22 2026 at 11:51 UTC
Internal: Dogfooding cleanup
Built-in detectors now use AST queries instead of text scanning, resolving a longstanding hypocrisy where Cha advertised AST-based analysis but several core plugins did substring matches that misfired on strings, comments, and unrelated identifiers.
Added
cha_core::query— host-side tree-sitter query helper (run_query/run_queries/node_to_match). Both built-in plugins and the WASMtree_queryhost import now go through this single API.DeadCodeAnalyzer::entry_points— entry-point names are now configurable via[plugins.dead_code] entry_points = [...]. Default list expanded from Rust-only (5 names) to multi-language (Rust + Python__init__etc + Goinit+ C_start+ tokio).LengthAnalyzer::complexity_factor_threshold— was hardcoded10.0, now configurable via[plugins.length].
Fixed
unsafe_api: rewritten from line-basedline.contains+ odd-quote-count heuristic to per-language tree-sitter queries. lvgl baseline picks up 19 realsprintf/strcpy/strcat/systemcall sites; previously zero. Comments and string literals containing keywords likeunsafeno longer false-positive.dead_code: substringis_in_file_referencedreplaced with AST identifier scan. Token-concat macro detection rewritten — instead of nuking the entire file when any#define ... ##exists, parse define bodies forprefix##X##suffixslots, scan call sites for invocation arguments, synthesize plausible expansion names, and add them to the reference set. lvgl thorvg'sSTYLE_DEFdispatch table no longer hides every dispatch function.IdentifierPositionslookup is now O(1) per symbol viaHashMap<name, Vec<line>>.error_handling:unwrap_abuseuses tree-sitter ((call_expression field_expression unwrap|expect)); empty-catch detection is per-language (Rust skipped, TScatch_clause, Pythonexcept_clause). String literals and comments containing the substringunwraporcatchno longer trigger.hardcoded_secret: regex matches now run againststring_literalnode text only, not full source lines. Comments and identifier names with secret-like substrings no longer false-positive.cha fix:String::replacewhole-content substitution replaced with tree-sitter identifier-node range collection + byte-offset reverse substitution. The previous implementation could rewrite identifier names inside string literals and comments, corrupting source files.git_metrics::check_test_ratio:f.contains("test") || f.contains("spec")replaced withcha_core::is_test_path. The substring check wrongly countedrequest.rs/spectrum.rsetc. as test files, polluting the test-to-production ratio that driveslow_test_ratio.wasm.rs::infer_file_role: replaced duplicate test-path heuristics withcha_core::is_test_path. WASM plugins'FileRole::Testclassification now matches the canonical convention used elsewhere (__tests__/,__mocks__/,.test.ts,.spec.ts).find_macro_invocation_args: word-boundary check added —STYLE_DEFno longer matchesSTYLE_DEFINEinvocations.
Removed
unsafe_apiis_in_stringheuristic — superseded by tree-sitter queries that distinguish string literals at the AST level.error_handlingline-baseddetect_empty_catch— replaced with grammar-aware queries.HostState::query_cache— query compilation now lives incha_core::query(compile-on-demand; LRU caching to be added if measurement warrants).
1.17.0 - 2026-05-21
v1.17.0
May 21 2026 at 17:46 UTC
Added
project_query::function_at(path, line, col)— new host import returning theFunctionInfowhose body contains the given position. Useful for tree-query–driven detectors that need to disambiguate which declared function a queried position belongs to.WasmPluginTest::option_list / option_bool / option_int / option_float— list and typed option setters in the test harness, replacing the previous string-onlyoption().
Changed (breaking for WASM plugins)
tree_query::QueryMatch.start_line/end_lineare now 1-based (was 0-based). Aligns withFunctionInfo/ClassInfo/CommentInfoline numbering — no more per-plugin off-by-one conversion. Inputs tonode_at(line, col)andnodes_in_range(start, end)are likewise 1-based now.- Existing plugins compiled against the pre-1.17 WIT will need to be rebuilt against the new SDK; instantiation will fail loudly otherwise.
Fixed
react-hooksexample plugin — false positives onhook_after_early_return(in sibling components and inside return expressions likereturn useState()) eliminated by switching toproject_query::function_atfor host-function disambiguation. Now reports 5 true positives / 0 false positives on the 6-component .tsx fixture (was 5 / 2).
Documentation
docs/plugin-development.md: added Line/Column convention note, Project Query API section, WASM Compatibility Cheatsheet (regex panics, no clock, no FS),cha plugin buildvscargo builddistinction, and new option helpers in Testing.
1.16.0 - 2026-05-21
v1.16.0
May 21 2026 at 11:35 UTC
Added
TsxParserincha-parser—.tsxfiles now route to a parser usingtree_sitter_typescript::LANGUAGE_TSX, so JSX nodes (jsx_element,jsx_attribute,jsx_self_closing_element) are first-class AST citizens. WASM plugins can now match them viatree_query::run_query.examples/wasm-plugin-react-hooks— example WASM plugin demonstratingtree_queryintegration. Detects 5 React Rules of Hooks violations: hooks called from non-component functions, hooks in conditionals, hooks in loops, hooks after early return, and hooks in nested callbacks.examples/wasm-plugin-todo-tracker— example WASM plugin demonstrating extended TODO comment tracking beyond the builtintodo_tracker. Adds 5 new smells: extended tag set (BUG/WIP/OPTIMIZE/PERF/DEPRECATED + user-configurable extras),(by:YYYY-MM-DD)expiration, priority escalation (!/!!/!!!), per-file TODO hotspot detection, and required-attribution policy.
Notes
- WIT unchanged at
cha:plugin@0.3.0(no breaking change). - Routing for
.ts/.mts/.ctscontinues to useLANGUAGE_TYPESCRIPT. Only.tsxswitched.
1.15.0 - 2026-05-14
v1.15.0
May 21 2026 at 07:43 UTC
Added
ProjectQuerytrait incha-core— plugins now access cross-file data through a typed interface onAnalysisContext.projectinstead of host-side post-hoc string-matched filtering. 12 methods cover the project-level queries existing post-analysis passes need:is_called_externally,callers_of,function_home/function_by_name/class_home,is_third_party,workspace_crate_names,is_test_path, etc. WASM plugins also gain access via theproject-queryhost import.ProjectQueryBulktrait extendsProjectQueryfor in-process iteration (iter_models); not exposed to WASM.cha_core::is_test_path— public utility consolidating two duplicated implementations.- example-wasm
unused_helpersmell — demonstratesproject_query::callers_ofcallback.
Changed
- WIT bumped to
cha:plugin@0.3.0(breaking) — addsproject-queryhost import. External plugins compiled against0.2.0must rebuild. large_api_surfaceC/C++ heuristics —.h/.hppheaders are now skipped (their 100% public surface is by design);.c/.cppimplementation files use a higher count threshold (30, configurable asc_max_exported_count) and the ratio gate is effectively off (configurable asc_max_exported_ratio). lvgl baseline: 393 → 34 findings (-91%).dead_codeis now project-aware — usesProjectQuery::is_called_externallyto confirm cross-file usage; the per-file text search is just an early shortcut. The token-concat macro heuristic (#define ... ##) remains because parsers don't macro-expand. lvgl baseline: 67 → 6 findings (-91%).
Removed
Plugin::cross_file_aware_smellstrait method — replaced by typed query throughAnalysisContext.project.cha-cli::cross_file_filtermodule — the post-hoc string-matched filter is gone; plugins produce final findings using the typed trait.- 3 duplicated
workspace_crate_namesimpls + 3 duplicatedis_third_party/is_external_leakimpls + 2 duplicatedis_test_pathimpls — all consolidated.
Added
- New
.cha.tomlconfig keys forapi_surface:max_exported_ratio,c_max_exported_count,c_max_exported_ratio,skip_c_headers. All language-aware defaults preserved.
1.14.0 - 2026-05-14
v1.14.0
May 14 2026 at 07:28 UTC
Added
- Plugin AST Query API — WASM plugins can now execute tree-sitter queries against the current file's AST via the
tree-queryhost import interface (run-query,run-queries,node-at,nodes-in-range). Enables plugins to do custom structural pattern matching without reimplementing parsing. file-roleenum inanalysis-input— host infers whether a file issource,test,doc,config, orgeneratedfrom its path, allowing plugins to apply differential detection strategies.- SourceModel enrichment —
analysis-inputnow includescomments,type-aliases,parameter-names,switch-arm-values, andis-module-declfields previously only available to internal plugins. parse_file_full()incha-parser— returnsParseResultcarrying model + tree-sitterTree+Languagefor downstream use by WASM host callbacks.
Changed
- WIT bumped to
cha:plugin@0.2.0— breaking change: plugins compiled against0.1.0must be recompiled. No behavioral change for existing internal plugins.
1.13.1 - 2026-04-30
v1.13.1
Apr 30 2026 at 09:48 UTC
Added
abstraction_leak_surgerydetector — files that co-change in git history and share a third-party type in their function signatures. Upgrade of the classicshotgun_surgery: instead of "these files always change together" (agnostic of why), this pinpoints "these files always change together because they all depend on the same external type" — the shared external type is the concrete abstraction leak driving the co-change cascade. SeverityHint.- Inputs: git co-change counts (
git log --name-only -N, threshold ≥ 5 commits in last 100) × per-fileTypeOrigin::Externalsets derived from parameter / return types. Workspace-sibling crates auto-whitelisted (same mechanismcross_boundary_chain/leaky_public_signatureuse), socha_core-internal dependencies betweencha-parser/cha-clidon't fire. - Cha self-baseline: 10 genuine findings, all pointing at the 5 language parsers sharing
tree_sitter::Node— exactly the abstraction leak the detector is designed to find (tree-sitter upgrades ripple across every parser file). lvglsrc/: 0 (C project, no External origins).
- Inputs: git co-change counts (
1.13.0 - 2026-04-30
v1.13.0
Apr 30 2026 at 09:05 UTC
Added
primitive_representationdetector (roadmap S8.2). Flags function parameters whose name carries a domain concept (user_id,email,status_code,api_url,password,language, …) but whose type is a raw scalar primitive (String,i32,bool,char, …). Signals an opportunity to introduce a newtype / value object to preserve the invariant. Per-parameter detection groups all offending params of one function into a single hint. Complements the existingprimitive_obsession(which looks at per-function ratio): this fires on even a single param when it's clearly a business concept.- Business-token and noise-token vocabularies are deliberately narrow to keep signal-to-noise high. Substring matches are ruled out (tokens must be standalone words —
widget_identifierdoes not trigger onid). - Parameters already typed with project-local newtypes (e.g.
id: UserIdwhereUserIdisTypeOrigin::Local) are skipped — the author already did the right thing. - Container types (
Path,PathBuf,Vec,Arc,Box,HashMap, …) are treated as domain-carrying and excluded; wrappingpath: &Pathin a newtype would destroy the abstraction. - Only runs on
is_exportedfunctions — private helpers are noise for a design signal aimed at public API hygiene. - Cha self-analyze: 14 findings (all genuine —
rel_path/env_hash/language/key/hashas raw types). lvglsrc/baseline: 53 findings (TTFplatformID/encodingID/languageID/nameID: int, file-explorerpath/dir: charpointers, …).
- Business-token and noise-token vocabularies are deliberately narrow to keep signal-to-noise high. Substring matches are ruled out (tokens must be standalone words —
stringly_typed_dispatchdetector (roadmap S8.8). Flags functions whoseswitch/matchbody dispatches on ≥ 3 string or ≥ 3 integer literal arms — classic "the arm values should have been an enum" smell. Char-literal arms (C tokenisers) skipped. Enum-variant / structural-pattern arms classify asOtherand never contribute to the threshold, somatch event { Event::Click => …, Event::Scroll => …, _ => … }stays quiet whilematch s { "click" => …, "scroll" => …, "submit" => … }fires. SeverityHint. Complements S8.2primitive_representation(signature side) with the body-side dispatch signal.- New
cha_core::ArmValueenum (Str / Int / Char / Other) +FunctionInfo.switch_arm_values+FunctionSymbol.switch_arm_values. Populated by every parser via a new sharedcha-parser/src/switch_arms.rshelper — language-specific arm-node kinds funnel through one classifier. - Cha self-baseline: 20 findings (all node-kind dispatchers in the 6 language parsers — valid detections, users can add
// cha:ignore stringly_typed_dispatchif the dispatch shape is forced by tree-sitter). lvglsrc/baseline: 23 findings (PNG/JPEG/QR error-code dispatchers, color-format size tables, TTF bytecode interpreter).
- New
cross_boundary_chaindetector (roadmap S8.U4). Flags functions wherechain_depth ≥ 3and the chain's root parameter is externally-typed (TypeOrigin::External(crate)) — the function is reaching into a third-party library's internal field layout, not just over-chaining local data. Companion to the existingmessage_chain(which fires on depth regardless of source):cross_boundary_chainis narrower but a stronger abstraction-leak signal. SeverityHint.- Workspace crates are auto-whitelisted (same mechanism
leaky_public_signatureuses), so siblingcha_core::Findingtraversals inside this repo don't fire. Cha self-baseline: 4 findings, all genuinetree_sitter::Nodetraversals incha-parser. lvglsrc/baseline: 0 (C project, fewExternalorigins by design). - Zero parser changes — reuses
chain_depth,parameter_types(with origin),parameter_names,external_refs. Pure post-pass onProjectIndex.
- Workspace crates are auto-whitelisted (same mechanism
FunctionInfo.parameter_names+FunctionSymbol.parameter_names(cha-core). Parallel toparameter_types: identifier names in declaration order. All six parsers (Rust / TS / Python / Go / C / C++) extract these;self/ C++thispositions skipped to stay length-aligned withparameter_types. Enables name-semantic analyses likeprimitive_representation, future LSP hover with full signatures, futurecha summary.- New helpers
cha_parser::rust_imports::rust_param_namesandcha_parser::cpp::c_param_nameextract identifier names from their language's declarator chains; reused across all C/C++ function-definition sites.
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 fromSourceModel. New type incha-core::modelcarrying the fields consumers likecha deps, LSP workspace-symbols, and futurecha summaryall share — class/function names + signatures + positions +type_aliases— without per-function-body data (complexity, body hash, TypeRef origin, cognitive, chain depth etc. stay inSourceModel).ProjectCache::{get,put}_symbolsstore tosymbols/{chash}.bin, mirrored independently ofparse/{chash}.bin. Sameenv_hashmechanism invalidates both on parser code changes.cached_symbols(path)is a new warm fast path that skipsSourceModeldeserialisation entirely —symbols/{chash}.binis roughly 10% the size ofparse/{chash}.bin.cached_parsenow populates both caches on every fresh parse, so the two views are always in lockstep.lvgl src/warm benchmarks (379 files):deps --type imports1.28s → 38ms (34×),--type classes1.30s → 56ms (23×),--type calls1.30s → 48ms (27×). Edge counts unchanged vs. pre-migration (1351/142/8109).cha-cli/src/c_oop_enrichgrows aenrich_c_oop_symbols/attribute_methods_by_name_from_symbolspair alongside the existingSourceModelfunctions. Sharedattribute_one_rawkeeps 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 bothcached_parseandcached_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(), destructorsFoo::~Foo(), operatorsFoo::operator+()) was silently dropped —find_func_name_nodeonly accepted bareidentifierdeclarators. It now also unwrapsqualified_identifier,destructor_name, andoperator_name.- Out-of-class method definitions now attribute to their owning same-file class:
void Foo::bar()bumpsClassInfo::method_countonFooand flipshas_behavior. Cross-file attribution still runs throughcha-cli::c_oop_enrich. namespace_definition,linkage_specification(extern "C" { ... }), andtemplate_declarationare 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.rssoc_lang.rsstays below thelarge_filegate.
SourceModel.type_aliasesnow populated for Rust, TypeScript, Python, and Go (previously all four returned emptyvec![]with parser-side TODOs). Each parser recognises its language's alias form and records(alias, rhs)pairs: Rusttype X = Y;/pub type X<T> = Y;, TypeScripttype X = Y;/export type X<T> = Y;, Python 3.12+type X = Yand pre-3.12X: TypeAlias = Y, Gotype X = Y(only the true alias form —type X Ydefined types are excluded). Plain PythonX = Yassignments remain unclassified (too ambiguous). Shared extraction lives in a newcha-parser/src/type_aliases.rsmodule so per-language files stay below thelarge_filegate.
Changed
boundary_leakdetector migrated toProjectIndex. 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 newcache::tests::cache_roundtrip_preserves_type_aliasesunit test makes the invariant a testable one.boundary_leak::detectnow takes&ProjectIndexand shares the same parse pass asanemic_domain_model,typed_intimacy,module_envy, and friends. Verified against lvgl'ssrc/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 qualifierFoo<int>(atemplate_typenode) didn't match the stored class nameFoo.attach_to_classnow strips trailing<...>template arguments before matching, so out-of-class specialisations attribute correctly. Same stripping applies to any declaration whose declarator surfacesFoo<...>as the owning scope. - C++: real inheritance (
class Derived : public Base) now recognised.extract_classconsults thebase_class_clausechild and pulls the firsttype_identifier(ortemplate_type's underlying name) asparent_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 sotemplate<typename T> class Foo {...}stores"Foo"instead of"Foo<T>". - C++: reference-return methods no longer vanish.
const int& Foo::get()and similarreference_declarator-wrapped definitions used to be silently dropped because tree-sitter-cpp'sreference_declaratorhas nodeclaratorfield — the declarator walker returnedNone. Bothfind_func_name_node(c_lang) anddescend_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 incha-parser/tests/cpp_enhancements.rs(14 total).
1.11.1 - 2026-04-27
v1.11.1
Apr 27 2026 at 08:18 UTC
Changed
- Internal: split git-backed post-analysis passes (
unstable_dependency,bus_factor,low_test_ratio) out ofcha-cli/src/analyze.rsinto a newcha-cli/src/git_metricsmodule. No behaviour change;analyze.rsdrops below the 850-linelarge_filethreshold thatcargo xtask analyzegates on.collect_top_levelin the C parser also picks up// cha:ignore high_complexityalongside the existing cognitive-complexity ignore after thedeclarationarm added one branch.
Note: 1.11.0 was tagged in the repo but the CI self-analyze gate failed on the above source-dir warnings so crates.io was never updated. 1.11.1 is the first shipped release of the 1.11 line.
1.10.0 - 2026-04-25
v1.10.0
Apr 25 2026 at 15:03 UTC
Added
god_config— flags aConfig/Settings/Options/Context/Env/AppState/Store-shaped type (exact name or*Config/*Settings/*Optionssuffix) passed as a parameter to ≥ 10 distinct functions spanning ≥ 3 files. Signals ambient configuration leaking everywhere instead of each caller taking only the fields it actually needs. Hint severity.circular_abstraction— flags two files whose functions call each other's functions in both directions (≥ 2 calls each way). Catches behaviour-level mutual dependency that import-graph cycle detection misses when the callees are re-exported or wrapped. Complementstyped_intimacy(type flow) with call flow. Hint severity.parameter_position_inconsistency— flags functions where a domain type appears at a different parameter position than the project-wide majority. Requires ≥ 3 usages of the same type across functions and disagreement on position; primitives, unresolved-origin types, mutable-ref out-params (&mut Vec<_>), andselfreceivers are skipped. Hint severity.
Changed
- Internal:
cha-cli/src/project_index.rs— sharedProjectIndexowns parsed models plus derived maps (function_home, class_home, project_type_names, function_by_name).anemic_domain_model,typed_intimacy,module_envy, andparameter_position_inconsistencybuild the index once per analyze call instead of each rebuilding their own copies. No behaviour change; behaviourally identical on self-analyze. Boundary_leak still parses fresh because of a stale-typedef cache bug not yet rooted out.
1.9.0 - 2026-04-25
v1.9.0
Apr 25 2026 at 14:25 UTC
Added
module_envy— flags a function that makes ≥ 3 calls into another file in the project while making ≤ half as many calls within its own file. The function is a "resident" of the wrong module — its body does work that belongs in the envied module. Suppresses test →common.rspairs and calls to conventional helper filenames (utils,helpers,shared,prelude, …) where cross-file dependency is idiomatic, not misplaced. Hint severity.typed_intimacy— flags file pairs whose function signatures exchange each other's declared types in both directions. Stronger signal than import-levelinappropriate_intimacy: the pair literally accepts/returns types defined in each other, indicating they're functionally fused at the type boundary. Emits one finding per side of the pair, listing the shared type names. Hint severity.async_callback_leak— flags a function signature that exposes a raw concurrency primitive (JoinHandle,Future,Task,Sender,Receiver,Promise,Awaitable,Coroutine,CancelFunc, …) in its return type or parameters. Skips launcher-shaped names (spawn_*,launch_*,start_*) where exposing the handle is the function's whole purpose. Hint severity.anemic_domain_model— flags a class that is pure data (≥ 2 fields, no behavior) paired with one or more external service-shaped functions (filename ends inservice/manager/handler/helper/util, or function name starts with a service verb prefix likeprocess_/validate_/calculate_) that take the class as a first parameter. Promotes adata_classhint into an architectural finding when there's evidence the paired service owns behavior that should live on the class itself. Hint severity.test_only_type_in_production— warns when production code references a class/struct declared only in test files (mocks, stubs, fixtures). Surfaces test scaffolding bleeding into shipping code. Warning severity.return_type_leakpost-analysis finding — dual ofabstraction_boundary_leak. Detects when a dispatcher fans out to ≥ 3 sibling handlers whose return types are all the same non-local type, surfacing missing Anti-Corruption Layer on the way out. lvgl scan identifies thorvg'sTVG_APIleaking through dispatcher boundaries.FunctionInfo.return_type: Option<TypeRef>— parsers extract the declared return type and resolve its origin through the same imports/type-registry pipeline as parameters. WIT schema grows an optionalreturn-typefield.- Container-expression primitive fallback: PEP 585
dict[K, V]/list[T]/tuple[...]resolve to Primitive instead of Unknown, eliminating false positives on Python handlers that return built-in container types.
Changed
- WIT
function-inforecord gainsreturn-type: option<type-ref>— breaking for WASM plugins, rebuild against the new SDK. cha-cli/src/analyze.rs— extracted C OOP false-positive filter toc_oop_filter.rsand splitrun_post_analysisinto git-based and signature-based helpers to keep the orchestrator lean as more post-analysis passes land.
1.8.0 - 2026-04-25
v1.8.0
Apr 25 2026 at 13:12 UTC
Added
abstraction_boundary_leakpost-analysis finding — detects dispatcher functions that fan out to ≥ 3 sibling callbacks which all share the same non-local type in corresponding parameter positions. Flagged as a missing Anti-Corruption Layer. lvgl scan shows 11/13 true-positive rate identifying GLAD/SDL/STB/Win32 leaks.FunctionInfo.parameter_typesnow carriesTypeRef { name, raw, origin }whereoriginisLocal | External(module) | Primitive | Unknown. Each parser resolves origins from file imports: Rustuse_declaration, TSimport_statement, Pythonimport/from, Goimport_specwithgo.modmodule root lookup, C/C++ primitive seeding.- Parser normalisation helpers in
cha-parser/src/type_ref.rsunwrap&'a mut Vec<Option<T>>,[]T,List[T],pkg.Typeetc. down to the innermost identifier for import lookup. - Universal-primitive fallback in resolve (String, PathBuf, HashMap, int, boolean, etc.) so common prelude types without explicit imports don't trip the detector.
unwrap_abusenow emits one finding per.unwrap()/.expect(call site (was: single finding at function name). IDE underlines each call directly.switch_statementnow points at theswitch/matchkeyword inside the function body (was: function name).message_chainnow points at thea.b.c.dchain expression itself (was: function name). Heuristic text scan, falls back to function name when the chain can't be textually located.
Changed
FunctionInfo.parameter_typestype changed fromVec<String>toVec<TypeRef>— breaking change for WASM plugins and cached SourceModels. WIT schema addstype-refrecord andtype-originvariant. Rebuilding against the new SDK picks up generated types automatically.- Parsers no longer sort
parameter_types— declaration order is preserved, fixing latent.first()-based C OOP heuristics that silently depended on alphabetical ordering.data_clumpsplugin now sorts its own key locally.
1.7.1 - 2026-04-24
v1.7.1
Apr 23 2026 at 19:38 UTC
Fixed
cargo xtask release—wait_for_workflownow filters runs by the commit SHA (for ci.yml) and the tag branch (for release.yml), instead of taking the latest run unconditionally. Previously a stale success on an unrelated commit would cause the release flow to skip waiting and publish to crates.io while the new CI was still queued; a stale failure would abort a release that would otherwise pass.
1.7.0 - 2026-04-23
v1.7.0
Apr 23 2026 at 15:12 UTC
Added
cha analyze --top Nflag — show only the N most severe findings (terminal format), complements--all- Smell-level disable:
disabled_smells = ["smell_name"]in.cha.toml(global) or under[languages.<lang>](language-scoped). Finer-grained than disabling a whole plugin when it produces multiple smells Plugin::smells()— plugins declare whichsmell_namevalues they can produce. Exposed as a WIT export for WASM pluginscha plugin listnow shows each plugin's declared smellscha preset show <lang>now shows effective disabled smells- SDK helper
cha_plugin_sdk::is_smell_disabled!(&input.options, "smell_name")— WASM plugins can skip disabled work proactively
Changed
- C/C++ builtin profile:
builder_pattern,null_object_pattern,strategy_pattern,data_clumpsare now properly disabled via smell-level config (previously tried — and failed — to disable them by plugin name) - WIT
analyzerworld gainssmells: func() -> list<string>export — breaking change for WASM plugins (recompile to pick up default impl)
Fixed
- lvgl-scale improvement: analyze now emits ~1200 fewer false positives because smell-level disables actually take effect
1.6.0 - 2026-04-23
v1.6.0
Apr 23 2026 at 06:12 UTC
Added
Locationnow hasstart_col/end_colfields — all findings precise to column levelFunctionInfo/ClassInfohavename_col/name_end_col— parser records identifier positionImportInfohascol— import statement column position- Terminal output shows
file:line:colwhen column info available - SARIF output fills
startColumn/endColumn(1-based per spec) - LSP diagnostics use precise column range
Changed
- All 37 builtin plugins now point findings at the function/class name, not the entire body
- Line-scanning plugins (unsafe_api, hardcoded_secret, todo_tracker, error_handling) report exact column
- WIT records gain column fields —
location.start-col/end-col,function-info.name-col/name-end-col,class-info.name-col/name-end-col,import-info.col— breaking change for WASM plugins
1.5.0 - 2026-04-22
v1.5.0
Apr 22 2026 at 11:49 UTC
Added
- VS Code
cha.disabledPluginssetting — suppress specific findings viainitializationOptions - Hover report card shows actual plugin findings with severity icons
- Coupling/hub_like findings mark import line range precisely
Changed
- LSP architecture: all handlers read from ProjectCache — no per-handler plugin execution
- LSP uses pull-only diagnostics (
textDocument/diagnostic), removed push duplicates - CodeLens shows findings count + severity instead of raw parse metrics
- Inlay Hints show findings summary (⚠N or ✓)
- File-level findings (large_file, shotgun_surgery, etc.) mark only line 1
Fixed
- Duplicate diagnostics (push + pull) in VS Code
disabledPluginsnow filters by finding name, not plugin name- LSP shares
.cha/cache/with CLI via ProjectCache
1.4.2 - 2026-04-22
v1.4.2
Apr 22 2026 at 03:29 UTC
Added
- VS Code: auto-detect outdated cha binary — prompt update when version mismatches extension
- VS Code: debug logs in ensureBinary for diagnostics
- VS Code e2e: real VS Code test on 3 platforms (ubuntu/macos/windows) with sinon stub for user Download click
Fixed
- SDK macros: include build.rs in package
- VS Code: Windows download (.zip + PowerShell + .exe)
- VS Code: exclude test files from .vsix via .vscodeignore
- CI: vscode e2e set continue-on-error for network flakiness
1.4.1 - 2026-04-21
v1.4.1
Apr 21 2026 at 12:38 UTC
Added
- VS Code extension CI:
vsce packagevalidation + download e2e test on GitHub Actions - Download e2e test imports actual extension code (shared
download.tsmodule)
Fixed
- Windows binary download: use
.zip+ PowerShell extraction +.exebinary name
1.4.0 - 2026-04-21
v1.4.0
Apr 21 2026 at 10:58 UTC
Added
- LSP Semantic Tokens: highlight functions/classes with warning modifier based on findings
- LSP Workspace Diagnostics: full project analysis without opening files
- LSP textDocument/diagnostic: pull-based diagnostics per file
- LSP Progress: progress notification during workspace diagnostics scan
1.3.0 - 2026-04-21
v1.3.0
Apr 21 2026 at 09:39 UTC
Added
- LSP Document Symbols: outline view with ⚠ markers based on actual findings severity
- LSP: Document Symbols ⚠ markers now respect
.cha.tomlthresholds (no hardcoded values)
Changed
- Upgraded wasmtime 43 → 44
- Include tests in cha-core crate package (eliminates publish warnings)
1.2.0 - 2026-04-21
v1.2.0
Apr 21 2026 at 08:51 UTC
Added
- LSP CodeLens: show complexity, cognitive, lines, params above every function/class
- LSP Hover: detailed quality report card on hover (markdown table)
- LSP Inlay Hints: inline cx/cog/lines annotations at end of function definitions
1.1.0 - 2026-04-21
v1.1.0
Apr 21 2026 at 07:56 UTC
Added
- Cache v2: bincode serialization + per-file parse cache + mtime fast-path
- L1 in-memory parse cache — zero disk I/O for repeated access within same process
- Cached imports in meta for instant
unstable_dependencyanalysis ProjectCachewith L1/L2 architecture shared across analyze/layers/deps/calibrate
Changed
- Performance:
cha analyze26x faster on warm cache (87s → 3.3s on 3201 files) - Performance:
cha layers16x faster (13s → 0.8s) - Performance:
cha deps14x faster (13s → 0.9s) - Performance:
cha calibrate22x faster (13s → 0.6s)
Fixed
- O(n²) algorithm in
unstable_dependency/compute_afferentreplaced with HashMap O(1) lookup - Findings cache wiped by duplicate
ProjectCache::openin post-analysis - Cache invalidation now includes cha binary version (upgrade = auto-invalidate)
- Skip
filter_c_oop_false_positiveswhen no lazy_class/data_class findings exist
1.0.10 - 2026-04-21
v1.0.10
Apr 21 2026 at 03:11 UTC
Added
- Global
--config <path>flag for all subcommands — load config from custom file ImportInfo.is_module_declfield to distinguish module declarations from imports
Fixed
- Rust
moddeclarations no longer inflatehigh_couplingcount
v1.0.8
v1.0.8
Apr 20 2026 at 09:38 UTC
Added
cha calibratecommand: auto-suggest thresholds from project statistics (P90/P95)cha calibrate --applysaves to.cha/calibration.toml, auto-applied bycha analyze- Finding priority sorting: most severe issues shown first (severity × overshoot × compound)
- Short module names in all output formats (terminal/DSM/dot/mermaid)
Changed
- DSM output limited to top 25 modules by file count
Fixed
- Skip parent→child layer violations (reduces lvgl false positives 87→37)
v1.0.7
v1.0.7
Apr 20 2026 at 08:29 UTC
Added
- Module inference rewrite: directory elbow + LCOM4 adaptive split + ICR + TCC quality metrics
cha layers --depth Nto override auto-detected directory depthcha layers --format dsm|terminaloutput formats- Composite risk scoring for
long_method:risk = lines_ratio × complexity_factor
Changed
- Module inference algorithm: replaced Union-Find with directory elbow + LCOM4 + ICR
long_methodseverity now based on composite risk (Hint/Warning/Error at risk 1/2/4)
Fixed
cha:ignoredirective now covers up to 2 lines before a function- Fixed corrupted dot output and switched to LR layout for better layer readability
v1.0.6
v1.0.6
Apr 19 2026 at 17:48 UTC
Added
- Language-adaptive thresholds: C/C++ profile with higher defaults (long_method=100, complexity=15, large_file=2000)
- Smart terminal aggregation: findings >5 grouped into summary + top 3 worst,
--allflag for full listing cha layerscommand: infer architectural layers from import dependenciescha layers --format dot|mermaid|json|plantumlwith layered architecture diagram
1.0.5 - 2026-04-17
v1.0.5
Apr 17 2026 at 14:39 UTC
Fixed
- VS Code extension: download URL corrected (
cha-cli-prefix), extract path for cargo-dist tarball - VS Code extension: download with progress bar and cancellation support
- VS Code extension: removed system PATH fallback for reliable self-testing
cargo publishno longer needs--allow-dirty(WIT copies tracked in git,includein Cargo.toml)
1.0.4 - 2026-04-17
v1.0.4
Apr 17 2026 at 13:15 UTC
Added
cha:setinline directive: override thresholds per-function/class via comments (// cha:set rule_name=value)Finding.actual_valueandFinding.thresholdfields for post-filter re-evaluationcha lspsubcommand: start LSP server from unified binary (+3MB)deps --direction in|out|both: filter edges by direction (who depends on target vs target depends on)deps --format plantuml: PlantUML output for component and class diagrams- C OOP false positive filter: removes lazy_class/data_class for structs with cross-file methods
.pre-commit-hooks.yaml: pre-commit framework integrationaction.yml: GitHub Action for CI analysis with SARIF upload- VS Code extension (
vscode-cha/): cha LSP integration, auto-download binary, esbuild bundle
Fixed
.hfiles with C++ constructs now parsed as C++ (content sniffing)class MACRO Name {}no longer misidentified as function definition- WIT
Findingrecord now includesactual_value/thresholdfields build.rsauto-copieswit/plugin.witfor crates.io packaging- VS Code extension: esbuild bundle, LICENSE,
.vscodeignore, publisher ID, homepage
v1.0.3
v1.0.3
Apr 17 2026 at 12:18 UTC