Cha

Releases

1.20.0 - 2026-06-05

v1.20.0 Jun 4 2026 at 19:45 UTC

🎉 First minor since v1.0 — and the first release with a proper home on the web. https://cha.to01.icu is now a real product page, not just a README dump. Star the repo, share the link, and tell your linter Cha said hi.

The analyzer itself didn't move — every --version-visible behavior is identical to v1.19.0. What landed is the documentation, presentation, and integration story: somewhere we can actually point new users.

Added

  • Bilingual documentation site at https://cha.to01.icu — landing page (oranda) plus a full mdbook tree at /book/ (English) and /book/zh-CN/ (中文). 60+ pages covering install, quick-start, every CLI subcommand, every output format, LSP integration for VS Code / Helix / Neovim / Zed, configuration reference, JSON Schema, plugin development, six worked recipes (migrate from clippy, monorepo CI, suppress in legacy code, custom plugin in 50 lines, calibrate to your codebase, baseline workflow), three contributor guides (architecture, writing a smell, releasing), and an academic-references page tracing every smell back to its source.
  • Cookbook recipes — six task-oriented walkthroughs: migrate-from-clippy, monorepo-ci, suppress-legacy, custom-plugin-50loc, calibrate, baseline. Each starts with a problem statement and ends with copy-pasteable commands. Authored in English and Chinese (the Chinese versions are written natively, not translated, so the prose style matches the rest of zh-CN).
  • Contributor guidescontributing/architecture.md documents the seven-crate workspace and data flow with mermaid diagrams; writing-a-smell.md walks through MiddleManAnalyzer as the worked example; releasing.md is the runbook for cargo xtask bumprelease.
  • Configuration reference — every .cha.toml key, grouped by plugin, with thresholds and defaults sourced from cha-core/src/plugins/. JSON Schema reference page documenting cha schema output and how to wire it into IDEs.
  • Bilingual landing page (LANDING.md) — hero with logo + tagline + three CTAs, six feature cards (detectors, WASM SDK, LSP, git-aware analysis, output formats, two-level cache), 30-second get-started block, smell-category table, and editor integration links. Replaces the previous "README dumped into the homepage" experience.
  • CJK-aware search via Pagefind 1.4 — replaces mdbook's bundled elasticlunr (which silently dropped non-ASCII tokens, leaving the zh-CN tree un-searchable). Press s or / on any docs page to open a modal that indexes both language trees.
  • Per-page social meta + branded OG card — every page emits og:title / og:image / twitter:card so Slack / iMessage / 微信 link previews show a real card instead of a 32×32 favicon. The 1200×628 card embeds the Cha logo and is regenerable via python3 static/gen_og_card.py.
  • Custom 404 page — site-wide warm-themed 404 with the Cha logo and shortcuts back into the live parts of the site, replacing GitHub Pages' generic gray 404.
  • Language switcher in mdbook header — the toolbar globe icon switches between EN and zh-CN trees and remembers per-page equivalents.
  • xtask docs-check — verifies every page referenced in book/src(-zh-CN)/SUMMARY.md actually exists; runs as part of cargo xtask ci so a broken SUMMARY can't ship.
  • xtask i18n-check — flags zh-CN pages whose git ctime trails their English counterpart, surfacing translation drift.
  • xtask docgen-cli — generates book/src/reference/cli-manual.md from cha help-markdown so the CLI manual page in docs always matches the binary's actual --help. Hidden cha help-markdown subcommand added for this.
  • VS Code extension page — full inventory of what each LSP capability looks like inside VS Code (wavy underlines, lightbulbs, code-lens overlays, inlay hints, hover cards, semantic-token modifier, status-bar workspace scan progress) plus the cha.disabledPlugins setting documentation.

Fixed

  • tree-sitter S-expression query link — the link in docs/plugin-development.md and the zh-CN translation pointed at /syntax-highlighting/queries, which 404s on the current tree-sitter docs site. Updated to /using-parsers/queries/.
  • docs/plugin-development.md FunctionInfo / ClassInfo field tables — were bare struct dumps; now per-field semantic tables (type + what each field actually drives) for both languages.
  • README plugin table — every entry now has an anchor link into docs/plugins.md's detailed description, in both README.md and README.zh-CN.md. The previous UnstableDependency row (which never matched a real detector) was removed and async_callback_leak added.

Changed

  • Project homepagehttps://cha.to01.icu is the canonical entry point. README still works on github.com but now points at the docs site for anything beyond the quick-start.
  • CI — the Web workflow builds the EN tree via oranda, re-builds the zh-CN tree via mdbook with MDBOOK_BOOK__SRC=src-zh-CN, indexes both with Pagefind, and restores public/CNAME + drops public/404.html after oranda's clean. Deploys via JamesIves/github-pages-deploy-action@v4.6.4.

Notes for upgraders

Nothing to do. cha analyze produces the same output, .cha.toml accepts the same keys, every CLI flag still works. This is a documentation release: the binary moves from "had a single README" to "had a 60-page bilingual docs site" without changing anything you'd notice from the terminal.

If you want to celebrate by reading something other than --help: https://cha.to01.icu.

1.19.0 - 2026-05-22

v1.19.0 May 22 2026 at 18:34 UTC

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. Smell counts unchanged, 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

Built-in detectors now use AST queries instead of text scanning. Several core plugins previously 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. Picks up real sprintf/strcpy/strcat/system call sites that the line-based heuristic missed. 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. X-macro dispatch tables (e.g. STYLE_DEF) no longer hide 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