Best-effort ranking metadata shared by CLI search/recall and MCP
runtime recall. Keeping confidence and age maps behind one loader makes
it harder for a callsite to apply confidence boosts while accidentally
skipping the half-life decay input.
Fallback for language_from_tags when tags carry no language hint:
scan a rule’s file_patterns. If every parseable language-bearing
pattern resolves to the same canonical language, return it.
Mixed-language pattern lists (["**/*.rs", "**/*.go"]) and universal
patterns (["**/*"]) return None, matching language_from_tags.
Extract a language string from a skill’s tags JSON (a stringified
array like ["rust", "async"]). Returns the first recognised language
tag (normalised to lower-case), or None if the tags are unparseable,
empty, or carry no language hint.
Build skill_id -> age_in_days map for the half-life decay applied at
retrieval time (effective_confidence). Age uses created_at when
present, falls back to updated_at so rules backfilled without a
created_at still get a sane age. Skills with neither column set
(shouldn’t happen in practice — both have NOT NULL defaults) are
omitted; the retrieval path treats absence as age_days = 0 so
behaviour matches the pre-plumbing default.
Build a skill_id -> confidence_score map used by retrieval to
boost or dampen rules at ranking time. Mirrors the SELECT shape of
load_rules_from_db but skips the heavy text columns since the
caller only needs the score; this stays a tight key/value query
so calling it on every hook fire stays cheap.
Derive a stable signature for an in-scope rule SET from its skill ids.
Order-independent: ids are sorted before hashing so the signature depends
only on membership, not on retrieval/iteration order. Returns None for
an empty set so the freshness check stays scope-agnostic when nothing is
filtered.