Sprint 21 closed: keyword-first search, meaning when the string misses
Until this sprint, memory_search was a contiguous SQL LIKE. A question that shared no literal substring with the stored entity — a Russian paraphrase of an English ADR title, a reordered identifier — returned nothing. Assistants then opened files they did not need.
Sprint 21 keeps the keyword path as the fast default and adds a bundled, offline embedding fallback when LIKE is empty (ADR-0020).
Keyword first, meaning when it misses
Exact id / name / summary hits are unchanged. Only an empty keyword result runs cosine over that project's vectors. There is no Hugging Face fetch at runtime: intfloat/multilingual-e5-small (int8 ONNX, 384-d) is vendored next to the process, one model per process, one memory.db per project. Unchanged entities are not re-embedded.
The number this sprint claimed to move, on a fixed bilingual suite with zero substring overlap:
| Path | Recall |
|---|---|
Keyword LIKE |
0 |
| Bundled e5 fallback | 1.0 |
Ranking that matches meaning, not heat
On a live ~170-entity graph, e5 compresses almost every row into cosine 0.76–0.82. Adding ADR-0009 vitality (up to +9) on top of that range returned the hottest components — the project node at strength 423 — instead of Sprint 21. Semantic fallback now ranks cosine first, keeps neighbours within 0.02 of the best hit, and uses vitality only as a tie-break.
Routine chore / ci / lockfile commits stay on the keyword path. feat / fix / ADR-touching commits remain eligible, with a small cosine penalty so a sprint or ADR of similar meaning wins the near-tie. ADR entities now carry a Context (else Decision) paragraph, so the embed-string is no longer title-only.
Dogfood query «найти знания когда точное совпадение строки ничего не даёт»: Sprint 21 first, sprint tasks next, important fix(scanner) commits still present, version bumps gone.
Licence and packaging
Meronq is Apache-2.0. The bundled model is MIT (intfloat); transformers.js is Apache-2.0. THIRD_PARTY_NOTICES.md and the model LICENSE travel with the weights. Desktop build:runtime copies packages/embeddings/models/ into installer resources. @huggingface/transformers stays external to sidecar.mjs — a packaged build that cannot resolve the WASM module remains keyword-only; MCP and pnpm dev use the local vendored weights.
End users do not download e5 from Hugging Face. Developers clone once run pnpm --filter @meronq/embeddings vendor-model. If weights are missing, search degrades to keywords without failing the session.
Review notes
Code review on the ranking diff (architecture focus): no medium+ blockers. Markdown stripping for ADR summaries was tightened so inline code (properties_json) survives into the embed-string. Weak-score hybrid ranking and cross-project search stay out of scope. Packaged-sidecar transformers resolution is the explicit follow-up so the installer matches MCP dogfood.
Principle
Keep the exact match when it exists. When it does not, look up meaning locally — and do not let heat or a version bump outrank the decision.