Shared matcher core migration
The drifting per-plugin fuzzy_matcher.py is retired. The pure matching primitives now live once in a shared FuzzyMatcherCore, vendored byte-identically into the inner folder as matching_core.py; FuzzyMatcher subclasses it and keeps only Stream-Mapparr's plugin-specific layer (zone routing, OTA callsign resolution, channel-DB loading). Stream-Mapparr was the migration canary.
- Vendored core is sha256-pinned (
scripts/core_manifest.json) and enforced by a parity gate (tests/test_core_parity.py) + golden gate (tests/test_matcher_golden.py) in CI;.gitattributespins it to LF.matching_core.pyships with the code — the plugin won't load without it. strip_bare_regionis a class-attr opt-in (off here — preserves bug-066 behavior).calculate_similarityuses a Python>= min_ratiogate (replacing rapidfuzzscore_cutoff); no change to live match decisions.
Normalization (ported from Lineuparr PR #13)
- Non-ASCII names no longer collapse to empty in
process_string_for_matching(NFKD-fold, keep alphanumerics). - Unicode box-bar delimiters (
┃/│) recognized for prefix stripping.
Packaging built with git archive (bug-087-safe forward-slash paths), validated with scripts/validate_zip.py (matching_core.py included).