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 fix walks all enabled plugins and asks each one. Adding fix support for a new smell is one trait override, not a host-side if 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.
  • DesignPatternAdvisor config — 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.rs walker skip-dirs — extended from {target, node_modules, dist} to also skip build, out, __pycache__, venv, .venv, vendor. Reduces unnecessary .cha.toml discovery work in polyglot repos.

Fixed

  • switch_statement / message_chain: replaced bespoke text scanners (find_switch_keyword, walk_chain and ~140 lines of hand-rolled tokenization) with tree-sitter queries against switch_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_intimacy import 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.rs table rendering: 3 metric labels were hardcoded across 3 separate println blocks. Adding a calibration metric now means one entry in a (label, samples) array.

Changed

  • cha fix delegates to Plugin::try_fix for every finding — no more filter(|f| f.smell_name == "naming_convention") on the host. Existing behavior preserved (NamingAnalyzer fixes naming_convention PascalCase violations); other plugins return None until 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 WASM tree_query host 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 + Go init + C _start + tokio).
  • LengthAnalyzer::complexity_factor_threshold — was hardcoded 10.0, now configurable via [plugins.length].

Fixed

  • unsafe_api: rewritten from line-based line.contains + odd-quote-count heuristic to per-language tree-sitter queries. lvgl baseline picks up 19 real sprintf/strcpy/strcat/system call sites; previously zero. Comments and string literals containing keywords like unsafe no longer false-positive.
  • dead_code: substring is_in_file_referenced replaced with AST identifier scan. Token-concat macro detection rewritten — instead of nuking the entire file when any #define ... ## exists, parse define bodies for prefix##X##suffix slots, scan call sites for invocation arguments, synthesize plausible expansion names, and add them to the reference set. lvgl thorvg's STYLE_DEF dispatch table no longer hides every dispatch function. IdentifierPositions lookup is now O(1) per symbol via HashMap<name, Vec<line>>.
  • error_handling: unwrap_abuse uses tree-sitter ((call_expression field_expression unwrap|expect)); empty-catch detection is per-language (Rust skipped, TS catch_clause, Python except_clause). String literals and comments containing the substring unwrap or catch no longer trigger.
  • hardcoded_secret: regex matches now run against string_literal node text only, not full source lines. Comments and identifier names with secret-like substrings no longer false-positive.
  • cha fix: String::replace whole-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 with cha_core::is_test_path. The substring check wrongly counted request.rs / spectrum.rs etc. as test files, polluting the test-to-production ratio that drives low_test_ratio.
  • wasm.rs::infer_file_role: replaced duplicate test-path heuristics with cha_core::is_test_path. WASM plugins' FileRole::Test classification now matches the canonical convention used elsewhere (__tests__/, __mocks__/, .test.ts, .spec.ts).
  • find_macro_invocation_args: word-boundary check added — STYLE_DEF no longer matches STYLE_DEFINE invocations.

Removed

  • unsafe_api is_in_string heuristic — superseded by tree-sitter queries that distinguish string literals at the AST level.
  • error_handling line-based detect_empty_catch — replaced with grammar-aware queries.
  • HostState::query_cache — query compilation now lives in cha_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 the FunctionInfo whose 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-only option().

Changed (breaking for WASM plugins)

  • tree_query::QueryMatch.start_line / end_line are now 1-based (was 0-based). Aligns with FunctionInfo / ClassInfo / CommentInfo line numbering — no more per-plugin off-by-one conversion. Inputs to node_at(line, col) and nodes_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-hooks example plugin — false positives on hook_after_early_return (in sibling components and inside return expressions like return useState()) eliminated by switching to project_query::function_at for 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 build vs cargo build distinction, and new option helpers in Testing.

1.16.0 - 2026-05-21

v1.16.0 May 21 2026 at 11:35 UTC

Added

  • TsxParser in cha-parser.tsx files now route to a parser using tree_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 via tree_query::run_query.
  • examples/wasm-plugin-react-hooks — example WASM plugin demonstrating tree_query integration. 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 builtin todo_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 / .cts continues to use LANGUAGE_TYPESCRIPT. Only .tsx switched.

1.15.0 - 2026-05-14

v1.15.0 May 21 2026 at 07:43 UTC

Added

  • ProjectQuery trait in cha-core — plugins now access cross-file data through a typed interface on AnalysisContext.project instead 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 the project-query host import.
  • ProjectQueryBulk trait extends ProjectQuery for in-process iteration (iter_models); not exposed to WASM.
  • cha_core::is_test_path — public utility consolidating two duplicated implementations.
  • example-wasm unused_helper smell — demonstrates project_query::callers_of callback.

Changed

  • WIT bumped to cha:plugin@0.3.0 (breaking) — adds project-query host import. External plugins compiled against 0.2.0 must rebuild.
  • large_api_surface C/C++ heuristics.h/.hpp headers are now skipped (their 100% public surface is by design); .c/.cpp implementation files use a higher count threshold (30, configurable as c_max_exported_count) and the ratio gate is effectively off (configurable as c_max_exported_ratio). lvgl baseline: 393 → 34 findings (-91%).
  • dead_code is now project-aware — uses ProjectQuery::is_called_externally to 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_smells trait method — replaced by typed query through AnalysisContext.project.
  • cha-cli::cross_file_filter module — the post-hoc string-matched filter is gone; plugins produce final findings using the typed trait.
  • 3 duplicated workspace_crate_names impls + 3 duplicated is_third_party/is_external_leak impls + 2 duplicated is_test_path impls — all consolidated.

Added

  • New .cha.toml config keys for api_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-query host import interface (run-query, run-queries, node-at, nodes-in-range). Enables plugins to do custom structural pattern matching without reimplementing parsing.
  • file-role enum in analysis-input — host infers whether a file is source, test, doc, config, or generated from its path, allowing plugins to apply differential detection strategies.
  • SourceModel enrichmentanalysis-input now includes comments, type-aliases, parameter-names, switch-arm-values, and is-module-decl fields previously only available to internal plugins.
  • parse_file_full() in cha-parser — returns ParseResult carrying model + tree-sitter Tree + Language for downstream use by WASM host callbacks.

Changed

  • WIT bumped to cha:plugin@0.2.0 — breaking change: plugins compiled against 0.1.0 must 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_surgery detector — files that co-change in git history and share a third-party type in their function signatures. Upgrade of the classic shotgun_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. Severity Hint.
    • Inputs: git co-change counts (git log --name-only -N, threshold ≥ 5 commits in last 100) × per-file TypeOrigin::External sets derived from parameter / return types. Workspace-sibling crates auto-whitelisted (same mechanism cross_boundary_chain / leaky_public_signature use), so cha_core-internal dependencies between cha-parser / cha-cli don'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). lvgl src/: 0 (C project, no External origins).

1.13.0 - 2026-04-30

v1.13.0 Apr 30 2026 at 09:05 UTC

Added

  • primitive_representation detector (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 existing primitive_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_identifier does not trigger on id).
    • Parameters already typed with project-local newtypes (e.g. id: UserId where UserId is TypeOrigin::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; wrapping path: &Path in a newtype would destroy the abstraction.
    • Only runs on is_exported functions — 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/hash as raw types). lvgl src/ baseline: 53 findings (TTF platformID/encodingID/languageID/nameID: int, file-explorer path/dir: char pointers, …).
  • stringly_typed_dispatch detector (roadmap S8.8). Flags functions whose switch/match body 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 as Other and never contribute to the threshold, so match event { Event::Click => …, Event::Scroll => …, _ => … } stays quiet while match s { "click" => …, "scroll" => …, "submit" => … } fires. Severity Hint. Complements S8.2 primitive_representation (signature side) with the body-side dispatch signal.
    • New cha_core::ArmValue enum (Str / Int / Char / Other) + FunctionInfo.switch_arm_values + FunctionSymbol.switch_arm_values. Populated by every parser via a new shared cha-parser/src/switch_arms.rs helper — 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_dispatch if the dispatch shape is forced by tree-sitter). lvgl src/ baseline: 23 findings (PNG/JPEG/QR error-code dispatchers, color-format size tables, TTF bytecode interpreter).
  • cross_boundary_chain detector (roadmap S8.U4). Flags functions where chain_depth ≥ 3 and 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 existing message_chain (which fires on depth regardless of source): cross_boundary_chain is narrower but a stronger abstraction-leak signal. Severity Hint.
    • Workspace crates are auto-whitelisted (same mechanism leaky_public_signature uses), so sibling cha_core::Finding traversals inside this repo don't fire. Cha self-baseline: 4 findings, all genuine tree_sitter::Node traversals in cha-parser. lvgl src/ baseline: 0 (C project, few External origins by design).
    • Zero parser changes — reuses chain_depth, parameter_types (with origin), parameter_names, external_refs. Pure post-pass on ProjectIndex.
  • FunctionInfo.parameter_names + FunctionSymbol.parameter_names (cha-core). Parallel to parameter_types: identifier names in declaration order. All six parsers (Rust / TS / Python / Go / C / C++) extract these; self / C++ this positions skipped to stay length-aligned with parameter_types. Enables name-semantic analyses like primitive_representation, future LSP hover with full signatures, future cha summary.
  • New helpers cha_parser::rust_imports::rust_param_names and cha_parser::cpp::c_param_name extract 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 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).

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 of cha-cli/src/analyze.rs into a new cha-cli/src/git_metrics module. No behaviour change; analyze.rs drops below the 850-line large_file threshold that cargo xtask analyze gates on. collect_top_level in the C parser also picks up // cha:ignore high_complexity alongside the existing cognitive-complexity ignore after the declaration arm 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 a Config/Settings/Options/Context/Env/AppState/Store-shaped type (exact name or *Config/*Settings/*Options suffix) 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. Complements typed_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<_>), and self receivers are skipped. Hint severity.

Changed

  • Internal: cha-cli/src/project_index.rs — shared ProjectIndex owns parsed models plus derived maps (function_home, class_home, project_type_names, function_by_name). anemic_domain_model, typed_intimacy, module_envy, and parameter_position_inconsistency build 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.rs pairs 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-level inappropriate_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 in service/manager/handler/helper/util, or function name starts with a service verb prefix like process_/validate_/calculate_) that take the class as a first parameter. Promotes a data_class hint 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_leak post-analysis finding — dual of abstraction_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's TVG_API leaking 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 optional return-type field.
  • 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-info record gains return-type: option<type-ref>breaking for WASM plugins, rebuild against the new SDK.
  • cha-cli/src/analyze.rs — extracted C OOP false-positive filter to c_oop_filter.rs and split run_post_analysis into 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_leak post-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_types now carries TypeRef { name, raw, origin } where origin is Local | External(module) | Primitive | Unknown. Each parser resolves origins from file imports: Rust use_declaration, TS import_statement, Python import / from, Go import_spec with go.mod module root lookup, C/C++ primitive seeding.
  • Parser normalisation helpers in cha-parser/src/type_ref.rs unwrap &'a mut Vec<Option<T>>, []T, List[T], pkg.Type etc. 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_abuse now emits one finding per .unwrap() / .expect( call site (was: single finding at function name). IDE underlines each call directly.
  • switch_statement now points at the switch / match keyword inside the function body (was: function name).
  • message_chain now points at the a.b.c.d chain expression itself (was: function name). Heuristic text scan, falls back to function name when the chain can't be textually located.

Changed

  • FunctionInfo.parameter_types type changed from Vec<String> to Vec<TypeRef>breaking change for WASM plugins and cached SourceModels. WIT schema adds type-ref record and type-origin variant. 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_clumps plugin 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 releasewait_for_workflow now 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 N flag — 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 which smell_name values they can produce. Exposed as a WIT export for WASM plugins
  • cha plugin list now shows each plugin's declared smells
  • cha 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_clumps are now properly disabled via smell-level config (previously tried — and failed — to disable them by plugin name)
  • WIT analyzer world gains smells: 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

  • Location now has start_col/end_col fields — all findings precise to column level
  • FunctionInfo/ClassInfo have name_col/name_end_col — parser records identifier position
  • ImportInfo has col — import statement column position
  • Terminal output shows file:line:col when 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.colbreaking change for WASM plugins

1.5.0 - 2026-04-22

v1.5.0 Apr 22 2026 at 11:49 UTC

Added

  • VS Code cha.disabledPlugins setting — suppress specific findings via initializationOptions
  • 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
  • disabledPlugins now 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 package validation + download e2e test on GitHub Actions
  • Download e2e test imports actual extension code (shared download.ts module)

Fixed

  • Windows binary download: use .zip + PowerShell extraction + .exe binary 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.toml thresholds (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_dependency analysis
  • ProjectCache with L1/L2 architecture shared across analyze/layers/deps/calibrate

Changed

  • Performance: cha analyze 26x faster on warm cache (87s → 3.3s on 3201 files)
  • Performance: cha layers 16x faster (13s → 0.8s)
  • Performance: cha deps 14x faster (13s → 0.9s)
  • Performance: cha calibrate 22x faster (13s → 0.6s)

Fixed

  • O(n²) algorithm in unstable_dependency / compute_afferent replaced with HashMap O(1) lookup
  • Findings cache wiped by duplicate ProjectCache::open in post-analysis
  • Cache invalidation now includes cha binary version (upgrade = auto-invalidate)
  • Skip filter_c_oop_false_positives when 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_decl field to distinguish module declarations from imports

Fixed

  • Rust mod declarations no longer inflate high_coupling count

v1.0.8

v1.0.8 Apr 20 2026 at 09:38 UTC

Added

  • cha calibrate command: auto-suggest thresholds from project statistics (P90/P95)
  • cha calibrate --apply saves to .cha/calibration.toml, auto-applied by cha 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 N to override auto-detected directory depth
  • cha layers --format dsm|terminal output 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_method severity now based on composite risk (Hint/Warning/Error at risk 1/2/4)

Fixed

  • cha:ignore directive 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, --all flag for full listing
  • cha layers command: infer architectural layers from import dependencies
  • cha layers --format dot|mermaid|json|plantuml with 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 publish no longer needs --allow-dirty (WIT copies tracked in git, include in Cargo.toml)

1.0.4 - 2026-04-17

v1.0.4 Apr 17 2026 at 13:15 UTC

Added

  • cha:set inline directive: override thresholds per-function/class via comments (// cha:set rule_name=value)
  • Finding.actual_value and Finding.threshold fields for post-filter re-evaluation
  • cha lsp subcommand: 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 integration
  • action.yml: GitHub Action for CI analysis with SARIF upload
  • VS Code extension (vscode-cha/): cha LSP integration, auto-download binary, esbuild bundle

Fixed

  • .h files with C++ constructs now parsed as C++ (content sniffing)
  • class MACRO Name {} no longer misidentified as function definition
  • WIT Finding record now includes actual_value/threshold fields
  • build.rs auto-copies wit/plugin.wit for 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